Andrefty commited on
Commit
9b08c94
·
verified ·
1 Parent(s): a1f0860

Upload DeBERTa-v3-large PII models, tokenizers, and code

Browse files
README.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - deberta
5
+ - pii-detection
6
+ - token-classification
7
+ - kaggle
8
+ datasets:
9
+ - pii-data-detection
10
+ metrics:
11
+ - f5-score
12
+ ---
13
+
14
+ # DeBERTa-v3-large PII Data Detection (Final Model)
15
+
16
+ This repository contains the fine-tuned DeBERTa-v3-large model checkpoint from the Kaggle PII Data Detection Competition.
17
+
18
+ ## Model Details
19
+ - **Base Architecture**: microsoft/deberta-v3-large
20
+ - **Task**: Token classification for Personally Identifiable Information (PII) detection
21
+ - **Max Sequence Length**: 4096
22
+ - **Training Setup**:
23
+ - Competition training data + moredata_dataset_fixed.json
24
+ - Optimizer: AdamW, lr=1e-5, weight_decay=0.0
25
+ - Batch size: 2
26
+ - Epochs: 5 (Fold 0)
27
+ - **Validation Metric**: **0.9692** at Epoch 3
28
+
29
+ ## Checkpoints Included
30
+ - `deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth` (1.74 GB): Model weights with highest validation score (0.9692).
31
+ - `deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_last.pth` (5.21 GB): Full training checkpoint including optimizer, scheduler, and scaler states.
32
+ - Tokenizer files (`spm.model`, `tokenizer.json`, `config.json`, etc.)
33
+ - Training and inference code under `code/`.
added_tokens.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "[MASK]": 128000
3
+ }
code/inference-single-model-no-pp-privatelb-0-963.ipynb ADDED
@@ -0,0 +1,1677 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "8020243e",
6
+ "metadata": {
7
+ "papermill": {
8
+ "duration": 0.003262,
9
+ "end_time": "2025-04-30T15:27:34.703550",
10
+ "exception": false,
11
+ "start_time": "2025-04-30T15:27:34.700288",
12
+ "status": "completed"
13
+ },
14
+ "tags": []
15
+ },
16
+ "source": [
17
+ "## Introduction\n",
18
+ "\n",
19
+ "Congratulations to everyone who persevered through this challenging competition!\n",
20
+ "\n",
21
+ "I've been eagerly looking forward to reading the top solutions, and now that the competition has finally concluded, I am thrilled.\n",
22
+ "\n",
23
+ "It has been a great learning journey for all of us. I am excited to share that I plan to publish a training notebook that demonstrates how to achieve a score of `Private Leaderboard 0.963` using a single model.\n",
24
+ "\n",
25
+ "[Training Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/training-single-model-to-get-privatelb-0-963/)\n",
26
+ "\n",
27
+ "And this notebook shows how to submit the model trained above.\n",
28
+ "\n",
29
+ "\n",
30
+ "## Acknowledge\n",
31
+ "\n",
32
+ "I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.\n",
33
+ "\n",
34
+ "For training a single model the above notebook shows, it takes less than 40min!"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 1,
40
+ "id": "4e44c5cf",
41
+ "metadata": {
42
+ "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19",
43
+ "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5",
44
+ "execution": {
45
+ "iopub.execute_input": "2025-04-30T15:27:34.710901Z",
46
+ "iopub.status.busy": "2025-04-30T15:27:34.710600Z",
47
+ "iopub.status.idle": "2025-04-30T15:27:56.410746Z",
48
+ "shell.execute_reply": "2025-04-30T15:27:56.410065Z"
49
+ },
50
+ "papermill": {
51
+ "duration": 21.706244,
52
+ "end_time": "2025-04-30T15:27:56.412829",
53
+ "exception": false,
54
+ "start_time": "2025-04-30T15:27:34.706585",
55
+ "status": "completed"
56
+ },
57
+ "tags": []
58
+ },
59
+ "outputs": [
60
+ {
61
+ "name": "stderr",
62
+ "output_type": "stream",
63
+ "text": [
64
+ "2025-04-30 15:27:44.649168: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
65
+ "2025-04-30 15:27:44.649298: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
66
+ "2025-04-30 15:27:44.786214: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n"
67
+ ]
68
+ }
69
+ ],
70
+ "source": [
71
+ "import re\n",
72
+ "import os\n",
73
+ "import json\n",
74
+ "import torch\n",
75
+ "import argparse\n",
76
+ "from pathlib import Path\n",
77
+ "from itertools import chain\n",
78
+ "\n",
79
+ "import numpy as np\n",
80
+ "import pandas as pd\n",
81
+ "from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification\n",
82
+ "from datasets import Dataset\n",
83
+ "from spacy.lang.en import English\n",
84
+ "\n",
85
+ "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')"
86
+ ]
87
+ },
88
+ {
89
+ "cell_type": "code",
90
+ "execution_count": 2,
91
+ "id": "d7fc24ad",
92
+ "metadata": {
93
+ "execution": {
94
+ "iopub.execute_input": "2025-04-30T15:27:56.421309Z",
95
+ "iopub.status.busy": "2025-04-30T15:27:56.420333Z",
96
+ "iopub.status.idle": "2025-04-30T15:27:57.444502Z",
97
+ "shell.execute_reply": "2025-04-30T15:27:57.443587Z"
98
+ },
99
+ "papermill": {
100
+ "duration": 1.030446,
101
+ "end_time": "2025-04-30T15:27:57.446580",
102
+ "exception": false,
103
+ "start_time": "2025-04-30T15:27:56.416134",
104
+ "status": "completed"
105
+ },
106
+ "tags": []
107
+ },
108
+ "outputs": [
109
+ {
110
+ "name": "stdout",
111
+ "output_type": "stream",
112
+ "text": [
113
+ "pii-dbtv3-large-9688 pii-detection-removal-from-educational-data\r\n"
114
+ ]
115
+ }
116
+ ],
117
+ "source": [
118
+ "!ls ../input/"
119
+ ]
120
+ },
121
+ {
122
+ "cell_type": "code",
123
+ "execution_count": 3,
124
+ "id": "dea14c4c",
125
+ "metadata": {
126
+ "execution": {
127
+ "iopub.execute_input": "2025-04-30T15:27:57.454606Z",
128
+ "iopub.status.busy": "2025-04-30T15:27:57.454035Z",
129
+ "iopub.status.idle": "2025-04-30T15:27:57.459265Z",
130
+ "shell.execute_reply": "2025-04-30T15:27:57.458458Z"
131
+ },
132
+ "papermill": {
133
+ "duration": 0.011012,
134
+ "end_time": "2025-04-30T15:27:57.460840",
135
+ "exception": false,
136
+ "start_time": "2025-04-30T15:27:57.449828",
137
+ "status": "completed"
138
+ },
139
+ "tags": []
140
+ },
141
+ "outputs": [],
142
+ "source": [
143
+ "backbone = 'microsoft/deberta-v3-large'\n",
144
+ "model_dir = '/kaggle/input/pii-dbtv3-large-9688'\n",
145
+ "th = 0.5\n",
146
+ "\n",
147
+ "id2label = {0: 'B-EMAIL', 1: 'B-ID_NUM', 2: 'B-NAME_STUDENT', 3: 'B-PHONE_NUM', 4: 'B-STREET_ADDRESS', 5: 'B-URL_PERSONAL', 6: 'B-USERNAME', 7: 'I-ID_NUM', 8: 'I-NAME_STUDENT', 9: 'I-PHONE_NUM', 10: 'I-STREET_ADDRESS', 11: 'I-URL_PERSONAL', 12: 'O'}"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": 4,
153
+ "id": "e1da943a",
154
+ "metadata": {
155
+ "execution": {
156
+ "iopub.execute_input": "2025-04-30T15:27:57.468276Z",
157
+ "iopub.status.busy": "2025-04-30T15:27:57.467797Z",
158
+ "iopub.status.idle": "2025-04-30T15:27:57.473293Z",
159
+ "shell.execute_reply": "2025-04-30T15:27:57.472311Z"
160
+ },
161
+ "papermill": {
162
+ "duration": 0.011058,
163
+ "end_time": "2025-04-30T15:27:57.474961",
164
+ "exception": false,
165
+ "start_time": "2025-04-30T15:27:57.463903",
166
+ "status": "completed"
167
+ },
168
+ "tags": []
169
+ },
170
+ "outputs": [],
171
+ "source": [
172
+ "def tokenize(example, tokenizer):\n",
173
+ " text = []\n",
174
+ " token_map = []\n",
175
+ " \n",
176
+ " idx = 0\n",
177
+ " \n",
178
+ " for t, ws in zip(example[\"tokens\"], example[\"trailing_whitespace\"]):\n",
179
+ " \n",
180
+ " text.append(t)\n",
181
+ " token_map.extend([idx]*len(t))\n",
182
+ " if ws:\n",
183
+ " text.append(\" \")\n",
184
+ " token_map.append(-1)\n",
185
+ " \n",
186
+ " idx += 1\n",
187
+ " \n",
188
+ " \n",
189
+ " tokenized = tokenizer(\"\".join(text), return_offsets_mapping=True, truncation=False)\n",
190
+ " \n",
191
+ " \n",
192
+ " return {\n",
193
+ " **tokenized,\n",
194
+ " \"token_map\": token_map,\n",
195
+ " }"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": 5,
201
+ "id": "e9da25fc",
202
+ "metadata": {
203
+ "execution": {
204
+ "iopub.execute_input": "2025-04-30T15:27:57.482197Z",
205
+ "iopub.status.busy": "2025-04-30T15:27:57.481934Z",
206
+ "iopub.status.idle": "2025-04-30T15:27:58.693956Z",
207
+ "shell.execute_reply": "2025-04-30T15:27:58.693020Z"
208
+ },
209
+ "papermill": {
210
+ "duration": 1.218282,
211
+ "end_time": "2025-04-30T15:27:58.696470",
212
+ "exception": false,
213
+ "start_time": "2025-04-30T15:27:57.478188",
214
+ "status": "completed"
215
+ },
216
+ "tags": []
217
+ },
218
+ "outputs": [
219
+ {
220
+ "name": "stdout",
221
+ "output_type": "stream",
222
+ "text": [
223
+ " "
224
+ ]
225
+ },
226
+ {
227
+ "data": {
228
+ "application/vnd.jupyter.widget-view+json": {
229
+ "model_id": "5951514148bd4f938c2559866f91be40",
230
+ "version_major": 2,
231
+ "version_minor": 0
232
+ },
233
+ "text/plain": [
234
+ "#0: 0%| | 0/5 [00:00<?, ?ex/s]"
235
+ ]
236
+ },
237
+ "metadata": {},
238
+ "output_type": "display_data"
239
+ },
240
+ {
241
+ "data": {
242
+ "application/vnd.jupyter.widget-view+json": {
243
+ "model_id": "88430d270ddf4976af32c09d1afb0ad8",
244
+ "version_major": 2,
245
+ "version_minor": 0
246
+ },
247
+ "text/plain": [
248
+ "#1: 0%| | 0/5 [00:00<?, ?ex/s]"
249
+ ]
250
+ },
251
+ "metadata": {},
252
+ "output_type": "display_data"
253
+ }
254
+ ],
255
+ "source": [
256
+ "with open(\"/kaggle/input/pii-detection-removal-from-educational-data/test.json\", \"r\") as f:\n",
257
+ " data = json.load(f)\n",
258
+ "\n",
259
+ "ds = Dataset.from_dict({\n",
260
+ " \"full_text\": [x[\"full_text\"] for x in data],\n",
261
+ " \"document\": [x[\"document\"] for x in data],\n",
262
+ " \"tokens\": [x[\"tokens\"] for x in data],\n",
263
+ " \"trailing_whitespace\": [x[\"trailing_whitespace\"] for x in data],\n",
264
+ "})\n",
265
+ "\n",
266
+ "tokenizer = AutoTokenizer.from_pretrained(os.path.join(model_dir, 'fold0'))\n",
267
+ "collator = DataCollatorForTokenClassification(tokenizer)\n",
268
+ "ds = ds.map(tokenize, fn_kwargs={\"tokenizer\": tokenizer}, num_proc=2)"
269
+ ]
270
+ },
271
+ {
272
+ "cell_type": "code",
273
+ "execution_count": 6,
274
+ "id": "babea7d6",
275
+ "metadata": {
276
+ "execution": {
277
+ "iopub.execute_input": "2025-04-30T15:27:58.705593Z",
278
+ "iopub.status.busy": "2025-04-30T15:27:58.704818Z",
279
+ "iopub.status.idle": "2025-04-30T15:27:58.716765Z",
280
+ "shell.execute_reply": "2025-04-30T15:27:58.715954Z"
281
+ },
282
+ "papermill": {
283
+ "duration": 0.018147,
284
+ "end_time": "2025-04-30T15:27:58.718406",
285
+ "exception": false,
286
+ "start_time": "2025-04-30T15:27:58.700259",
287
+ "status": "completed"
288
+ },
289
+ "tags": []
290
+ },
291
+ "outputs": [
292
+ {
293
+ "data": {
294
+ "text/plain": [
295
+ "dict_keys(['full_text', 'document', 'tokens', 'trailing_whitespace', 'input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'token_map'])"
296
+ ]
297
+ },
298
+ "execution_count": 6,
299
+ "metadata": {},
300
+ "output_type": "execute_result"
301
+ }
302
+ ],
303
+ "source": [
304
+ "ds[0].keys()"
305
+ ]
306
+ },
307
+ {
308
+ "cell_type": "code",
309
+ "execution_count": 7,
310
+ "id": "e8da3b76",
311
+ "metadata": {
312
+ "execution": {
313
+ "iopub.execute_input": "2025-04-30T15:27:58.727230Z",
314
+ "iopub.status.busy": "2025-04-30T15:27:58.726677Z",
315
+ "iopub.status.idle": "2025-04-30T15:28:24.372489Z",
316
+ "shell.execute_reply": "2025-04-30T15:28:24.371483Z"
317
+ },
318
+ "papermill": {
319
+ "duration": 25.652138,
320
+ "end_time": "2025-04-30T15:28:24.374509",
321
+ "exception": false,
322
+ "start_time": "2025-04-30T15:27:58.722371",
323
+ "status": "completed"
324
+ },
325
+ "tags": []
326
+ },
327
+ "outputs": [
328
+ {
329
+ "data": {
330
+ "text/plain": [
331
+ "DebertaV2ForTokenClassification(\n",
332
+ " (deberta): DebertaV2Model(\n",
333
+ " (embeddings): DebertaV2Embeddings(\n",
334
+ " (word_embeddings): Embedding(128100, 1024, padding_idx=0)\n",
335
+ " (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
336
+ " (dropout): StableDropout()\n",
337
+ " )\n",
338
+ " (encoder): DebertaV2Encoder(\n",
339
+ " (layer): ModuleList(\n",
340
+ " (0-23): 24 x DebertaV2Layer(\n",
341
+ " (attention): DebertaV2Attention(\n",
342
+ " (self): DisentangledSelfAttention(\n",
343
+ " (query_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
344
+ " (key_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
345
+ " (value_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
346
+ " (pos_dropout): StableDropout()\n",
347
+ " (dropout): StableDropout()\n",
348
+ " )\n",
349
+ " (output): DebertaV2SelfOutput(\n",
350
+ " (dense): Linear(in_features=1024, out_features=1024, bias=True)\n",
351
+ " (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
352
+ " (dropout): StableDropout()\n",
353
+ " )\n",
354
+ " )\n",
355
+ " (intermediate): DebertaV2Intermediate(\n",
356
+ " (dense): Linear(in_features=1024, out_features=4096, bias=True)\n",
357
+ " (intermediate_act_fn): GELUActivation()\n",
358
+ " )\n",
359
+ " (output): DebertaV2Output(\n",
360
+ " (dense): Linear(in_features=4096, out_features=1024, bias=True)\n",
361
+ " (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
362
+ " (dropout): StableDropout()\n",
363
+ " )\n",
364
+ " )\n",
365
+ " )\n",
366
+ " (rel_embeddings): Embedding(512, 1024)\n",
367
+ " (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
368
+ " )\n",
369
+ " )\n",
370
+ " (dropout): Dropout(p=0.1, inplace=False)\n",
371
+ " (classifier): Linear(in_features=1024, out_features=13, bias=True)\n",
372
+ ")"
373
+ ]
374
+ },
375
+ "execution_count": 7,
376
+ "metadata": {},
377
+ "output_type": "execute_result"
378
+ }
379
+ ],
380
+ "source": [
381
+ "def get_model(conf):\n",
382
+ " return AutoModelForTokenClassification.from_config(\n",
383
+ " conf\n",
384
+ " )\n",
385
+ "\n",
386
+ "fold = 0\n",
387
+ "conf = AutoConfig.from_pretrained(os.path.join(model_dir, f'fold{fold}'))\n",
388
+ "model = get_model(conf)\n",
389
+ "model_file = os.path.join(model_dir, f'fold{fold}', f'genv1_noext1_ds3_ht1024_dbtv3_large_wu0.1_w10_ado4_wd0_bs2_lr1e5_5f_5epo_fold{fold}_best.pth')\n",
390
+ "sd = torch.load(model_file, map_location='cpu')\n",
391
+ "model.load_state_dict(sd, strict=True)\n",
392
+ "model.eval()\n",
393
+ "model.cuda()\n"
394
+ ]
395
+ },
396
+ {
397
+ "cell_type": "code",
398
+ "execution_count": 8,
399
+ "id": "d6469695",
400
+ "metadata": {
401
+ "execution": {
402
+ "iopub.execute_input": "2025-04-30T15:28:24.383514Z",
403
+ "iopub.status.busy": "2025-04-30T15:28:24.382837Z",
404
+ "iopub.status.idle": "2025-04-30T15:28:25.508686Z",
405
+ "shell.execute_reply": "2025-04-30T15:28:25.507737Z"
406
+ },
407
+ "papermill": {
408
+ "duration": 1.132525,
409
+ "end_time": "2025-04-30T15:28:25.510805",
410
+ "exception": false,
411
+ "start_time": "2025-04-30T15:28:24.378280",
412
+ "status": "completed"
413
+ },
414
+ "tags": []
415
+ },
416
+ "outputs": [
417
+ {
418
+ "name": "stdout",
419
+ "output_type": "stream",
420
+ "text": [
421
+ "Wed Apr 30 15:28:25 2025 \r\n",
422
+ "+-----------------------------------------------------------------------------------------+\r\n",
423
+ "| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |\r\n",
424
+ "|-----------------------------------------+------------------------+----------------------+\r\n",
425
+ "| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n",
426
+ "| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\r\n",
427
+ "| | | MIG M. |\r\n",
428
+ "|=========================================+========================+======================|\r\n",
429
+ "| 0 Tesla P100-PCIE-16GB Off | 00000000:00:04.0 Off | 0 |\r\n",
430
+ "| N/A 38C P0 31W / 250W | 1933MiB / 16384MiB | 0% Default |\r\n",
431
+ "| | | N/A |\r\n",
432
+ "+-----------------------------------------+------------------------+----------------------+\r\n",
433
+ " \r\n",
434
+ "+-----------------------------------------------------------------------------------------+\r\n",
435
+ "| Processes: |\r\n",
436
+ "| GPU GI CI PID Type Process name GPU Memory |\r\n",
437
+ "| ID ID Usage |\r\n",
438
+ "|=========================================================================================|\r\n",
439
+ "+-----------------------------------------------------------------------------------------+\r\n"
440
+ ]
441
+ }
442
+ ],
443
+ "source": [
444
+ "!nvidia-smi"
445
+ ]
446
+ },
447
+ {
448
+ "cell_type": "code",
449
+ "execution_count": 9,
450
+ "id": "22f8828a",
451
+ "metadata": {
452
+ "execution": {
453
+ "iopub.execute_input": "2025-04-30T15:28:25.520396Z",
454
+ "iopub.status.busy": "2025-04-30T15:28:25.519800Z",
455
+ "iopub.status.idle": "2025-04-30T15:28:28.012067Z",
456
+ "shell.execute_reply": "2025-04-30T15:28:28.011227Z"
457
+ },
458
+ "papermill": {
459
+ "duration": 2.499221,
460
+ "end_time": "2025-04-30T15:28:28.014108",
461
+ "exception": false,
462
+ "start_time": "2025-04-30T15:28:25.514887",
463
+ "status": "completed"
464
+ },
465
+ "tags": []
466
+ },
467
+ "outputs": [],
468
+ "source": [
469
+ "preds_final = []\n",
470
+ "with torch.no_grad():\n",
471
+ " for sample in ds:\n",
472
+ " input_ids = torch.tensor([sample['input_ids']]).to(device)\n",
473
+ " attention_mask = torch.tensor([sample['attention_mask']]).to(device)\n",
474
+ "\n",
475
+ " preds = model(input_ids, attention_mask=attention_mask, labels=None)['logits'].softmax(2)\n",
476
+ " preds[:, :, -1][preds[:, :, -1] < th] = 0\n",
477
+ " preds = torch.argmax(preds, axis=2).cpu().numpy()\n",
478
+ " preds_final.append(preds[0])\n"
479
+ ]
480
+ },
481
+ {
482
+ "cell_type": "code",
483
+ "execution_count": 10,
484
+ "id": "9c2a0aed",
485
+ "metadata": {
486
+ "execution": {
487
+ "iopub.execute_input": "2025-04-30T15:28:28.022790Z",
488
+ "iopub.status.busy": "2025-04-30T15:28:28.022520Z",
489
+ "iopub.status.idle": "2025-04-30T15:28:29.163583Z",
490
+ "shell.execute_reply": "2025-04-30T15:28:29.162604Z"
491
+ },
492
+ "papermill": {
493
+ "duration": 1.147856,
494
+ "end_time": "2025-04-30T15:28:29.165845",
495
+ "exception": false,
496
+ "start_time": "2025-04-30T15:28:28.017989",
497
+ "status": "completed"
498
+ },
499
+ "tags": []
500
+ },
501
+ "outputs": [
502
+ {
503
+ "name": "stdout",
504
+ "output_type": "stream",
505
+ "text": [
506
+ "Wed Apr 30 15:28:29 2025 \r\n",
507
+ "+-----------------------------------------------------------------------------------------+\r\n",
508
+ "| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |\r\n",
509
+ "|-----------------------------------------+------------------------+----------------------+\r\n",
510
+ "| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n",
511
+ "| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\r\n",
512
+ "| | | MIG M. |\r\n",
513
+ "|=========================================+========================+======================|\r\n",
514
+ "| 0 Tesla P100-PCIE-16GB Off | 00000000:00:04.0 Off | 0 |\r\n",
515
+ "| N/A 39C P0 36W / 250W | 4065MiB / 16384MiB | 0% Default |\r\n",
516
+ "| | | N/A |\r\n",
517
+ "+-----------------------------------------+------------------------+----------------------+\r\n",
518
+ " \r\n",
519
+ "+-----------------------------------------------------------------------------------------+\r\n",
520
+ "| Processes: |\r\n",
521
+ "| GPU GI CI PID Type Process name GPU Memory |\r\n",
522
+ "| ID ID Usage |\r\n",
523
+ "|=========================================================================================|\r\n",
524
+ "+-----------------------------------------------------------------------------------------+\r\n"
525
+ ]
526
+ }
527
+ ],
528
+ "source": [
529
+ "!nvidia-smi"
530
+ ]
531
+ },
532
+ {
533
+ "cell_type": "code",
534
+ "execution_count": 11,
535
+ "id": "710d5fa1",
536
+ "metadata": {
537
+ "execution": {
538
+ "iopub.execute_input": "2025-04-30T15:28:29.175351Z",
539
+ "iopub.status.busy": "2025-04-30T15:28:29.174744Z",
540
+ "iopub.status.idle": "2025-04-30T15:28:29.229118Z",
541
+ "shell.execute_reply": "2025-04-30T15:28:29.228370Z"
542
+ },
543
+ "papermill": {
544
+ "duration": 0.061412,
545
+ "end_time": "2025-04-30T15:28:29.231214",
546
+ "exception": false,
547
+ "start_time": "2025-04-30T15:28:29.169802",
548
+ "status": "completed"
549
+ },
550
+ "tags": []
551
+ },
552
+ "outputs": [],
553
+ "source": [
554
+ "pairs = []\n",
555
+ "processed = []\n",
556
+ "for p, token_map, offsets, tokens, doc in zip(preds_final, ds[\"token_map\"],\n",
557
+ " ds[\"offset_mapping\"], ds[\"tokens\"],\n",
558
+ " ds[\"document\"]):\n",
559
+ " for token_pred, (start_idx, end_idx) in zip(p, offsets):\n",
560
+ " label_pred = id2label[token_pred]\n",
561
+ "\n",
562
+ " if start_idx + end_idx == 0:\n",
563
+ " continue\n",
564
+ "\n",
565
+ " if token_map[start_idx] == -1:\n",
566
+ " start_idx += 1\n",
567
+ "\n",
568
+ " # ignore \"\\n\\n\"\n",
569
+ " while start_idx < len(token_map) and tokens[token_map[start_idx]].isspace():\n",
570
+ " start_idx += 1\n",
571
+ "\n",
572
+ " if start_idx >= len(token_map):\n",
573
+ " break\n",
574
+ "\n",
575
+ " token_id = token_map[start_idx]\n",
576
+ "\n",
577
+ " # ignore \"O\" predictions and whitespace preds\n",
578
+ " if label_pred not in (\"O\") and token_id != -1:\n",
579
+ "# if label_pred not in (\"O\", \"B-EMAIL\", \"B-PHONE_NUM\", \"I-PHONE_NUM\") and token_id != -1:\n",
580
+ " pair = (doc, token_id)\n",
581
+ "\n",
582
+ " if pair not in pairs:\n",
583
+ " processed.append({\"document\": doc, \"token\": token_id, \"label\": label_pred, \"token_str\": tokens[token_id]})\n",
584
+ " pairs.append(pair)"
585
+ ]
586
+ },
587
+ {
588
+ "cell_type": "code",
589
+ "execution_count": 12,
590
+ "id": "551a878b",
591
+ "metadata": {
592
+ "execution": {
593
+ "iopub.execute_input": "2025-04-30T15:28:29.241209Z",
594
+ "iopub.status.busy": "2025-04-30T15:28:29.240471Z",
595
+ "iopub.status.idle": "2025-04-30T15:28:29.262801Z",
596
+ "shell.execute_reply": "2025-04-30T15:28:29.261886Z"
597
+ },
598
+ "papermill": {
599
+ "duration": 0.0292,
600
+ "end_time": "2025-04-30T15:28:29.264560",
601
+ "exception": false,
602
+ "start_time": "2025-04-30T15:28:29.235360",
603
+ "status": "completed"
604
+ },
605
+ "tags": []
606
+ },
607
+ "outputs": [
608
+ {
609
+ "data": {
610
+ "text/html": [
611
+ "<div>\n",
612
+ "<style scoped>\n",
613
+ " .dataframe tbody tr th:only-of-type {\n",
614
+ " vertical-align: middle;\n",
615
+ " }\n",
616
+ "\n",
617
+ " .dataframe tbody tr th {\n",
618
+ " vertical-align: top;\n",
619
+ " }\n",
620
+ "\n",
621
+ " .dataframe thead th {\n",
622
+ " text-align: right;\n",
623
+ " }\n",
624
+ "</style>\n",
625
+ "<table border=\"1\" class=\"dataframe\">\n",
626
+ " <thead>\n",
627
+ " <tr style=\"text-align: right;\">\n",
628
+ " <th></th>\n",
629
+ " <th>document</th>\n",
630
+ " <th>token</th>\n",
631
+ " <th>label</th>\n",
632
+ " <th>token_str</th>\n",
633
+ " <th>row_id</th>\n",
634
+ " </tr>\n",
635
+ " </thead>\n",
636
+ " <tbody>\n",
637
+ " <tr>\n",
638
+ " <th>0</th>\n",
639
+ " <td>7</td>\n",
640
+ " <td>9</td>\n",
641
+ " <td>B-NAME_STUDENT</td>\n",
642
+ " <td>Nathalie</td>\n",
643
+ " <td>0</td>\n",
644
+ " </tr>\n",
645
+ " <tr>\n",
646
+ " <th>1</th>\n",
647
+ " <td>7</td>\n",
648
+ " <td>10</td>\n",
649
+ " <td>I-NAME_STUDENT</td>\n",
650
+ " <td>Sylla</td>\n",
651
+ " <td>1</td>\n",
652
+ " </tr>\n",
653
+ " <tr>\n",
654
+ " <th>2</th>\n",
655
+ " <td>7</td>\n",
656
+ " <td>482</td>\n",
657
+ " <td>B-NAME_STUDENT</td>\n",
658
+ " <td>Nathalie</td>\n",
659
+ " <td>2</td>\n",
660
+ " </tr>\n",
661
+ " <tr>\n",
662
+ " <th>3</th>\n",
663
+ " <td>7</td>\n",
664
+ " <td>483</td>\n",
665
+ " <td>I-NAME_STUDENT</td>\n",
666
+ " <td>Sylla</td>\n",
667
+ " <td>3</td>\n",
668
+ " </tr>\n",
669
+ " <tr>\n",
670
+ " <th>4</th>\n",
671
+ " <td>7</td>\n",
672
+ " <td>741</td>\n",
673
+ " <td>B-NAME_STUDENT</td>\n",
674
+ " <td>Nathalie</td>\n",
675
+ " <td>4</td>\n",
676
+ " </tr>\n",
677
+ " <tr>\n",
678
+ " <th>5</th>\n",
679
+ " <td>7</td>\n",
680
+ " <td>742</td>\n",
681
+ " <td>I-NAME_STUDENT</td>\n",
682
+ " <td>Sylla</td>\n",
683
+ " <td>5</td>\n",
684
+ " </tr>\n",
685
+ " <tr>\n",
686
+ " <th>6</th>\n",
687
+ " <td>10</td>\n",
688
+ " <td>0</td>\n",
689
+ " <td>B-NAME_STUDENT</td>\n",
690
+ " <td>Diego</td>\n",
691
+ " <td>6</td>\n",
692
+ " </tr>\n",
693
+ " <tr>\n",
694
+ " <th>7</th>\n",
695
+ " <td>10</td>\n",
696
+ " <td>1</td>\n",
697
+ " <td>I-NAME_STUDENT</td>\n",
698
+ " <td>Estrada</td>\n",
699
+ " <td>7</td>\n",
700
+ " </tr>\n",
701
+ " <tr>\n",
702
+ " <th>8</th>\n",
703
+ " <td>10</td>\n",
704
+ " <td>464</td>\n",
705
+ " <td>B-NAME_STUDENT</td>\n",
706
+ " <td>Diego</td>\n",
707
+ " <td>8</td>\n",
708
+ " </tr>\n",
709
+ " <tr>\n",
710
+ " <th>9</th>\n",
711
+ " <td>10</td>\n",
712
+ " <td>465</td>\n",
713
+ " <td>I-NAME_STUDENT</td>\n",
714
+ " <td>Estrada</td>\n",
715
+ " <td>9</td>\n",
716
+ " </tr>\n",
717
+ " <tr>\n",
718
+ " <th>10</th>\n",
719
+ " <td>16</td>\n",
720
+ " <td>4</td>\n",
721
+ " <td>B-NAME_STUDENT</td>\n",
722
+ " <td>Gilberto</td>\n",
723
+ " <td>10</td>\n",
724
+ " </tr>\n",
725
+ " <tr>\n",
726
+ " <th>11</th>\n",
727
+ " <td>16</td>\n",
728
+ " <td>5</td>\n",
729
+ " <td>I-NAME_STUDENT</td>\n",
730
+ " <td>Gamboa</td>\n",
731
+ " <td>11</td>\n",
732
+ " </tr>\n",
733
+ " <tr>\n",
734
+ " <th>12</th>\n",
735
+ " <td>20</td>\n",
736
+ " <td>5</td>\n",
737
+ " <td>B-NAME_STUDENT</td>\n",
738
+ " <td>Sindy</td>\n",
739
+ " <td>12</td>\n",
740
+ " </tr>\n",
741
+ " <tr>\n",
742
+ " <th>13</th>\n",
743
+ " <td>20</td>\n",
744
+ " <td>6</td>\n",
745
+ " <td>I-NAME_STUDENT</td>\n",
746
+ " <td>Samaca</td>\n",
747
+ " <td>13</td>\n",
748
+ " </tr>\n",
749
+ " <tr>\n",
750
+ " <th>14</th>\n",
751
+ " <td>56</td>\n",
752
+ " <td>12</td>\n",
753
+ " <td>B-NAME_STUDENT</td>\n",
754
+ " <td>Nadine</td>\n",
755
+ " <td>14</td>\n",
756
+ " </tr>\n",
757
+ " <tr>\n",
758
+ " <th>15</th>\n",
759
+ " <td>56</td>\n",
760
+ " <td>13</td>\n",
761
+ " <td>I-NAME_STUDENT</td>\n",
762
+ " <td>Born</td>\n",
763
+ " <td>15</td>\n",
764
+ " </tr>\n",
765
+ " <tr>\n",
766
+ " <th>16</th>\n",
767
+ " <td>86</td>\n",
768
+ " <td>6</td>\n",
769
+ " <td>B-NAME_STUDENT</td>\n",
770
+ " <td>Eladio</td>\n",
771
+ " <td>16</td>\n",
772
+ " </tr>\n",
773
+ " <tr>\n",
774
+ " <th>17</th>\n",
775
+ " <td>86</td>\n",
776
+ " <td>7</td>\n",
777
+ " <td>I-NAME_STUDENT</td>\n",
778
+ " <td>Amaya</td>\n",
779
+ " <td>17</td>\n",
780
+ " </tr>\n",
781
+ " <tr>\n",
782
+ " <th>18</th>\n",
783
+ " <td>93</td>\n",
784
+ " <td>0</td>\n",
785
+ " <td>B-NAME_STUDENT</td>\n",
786
+ " <td>Silvia</td>\n",
787
+ " <td>18</td>\n",
788
+ " </tr>\n",
789
+ " <tr>\n",
790
+ " <th>19</th>\n",
791
+ " <td>93</td>\n",
792
+ " <td>1</td>\n",
793
+ " <td>I-NAME_STUDENT</td>\n",
794
+ " <td>Villalobos</td>\n",
795
+ " <td>19</td>\n",
796
+ " </tr>\n",
797
+ " <tr>\n",
798
+ " <th>20</th>\n",
799
+ " <td>104</td>\n",
800
+ " <td>8</td>\n",
801
+ " <td>B-NAME_STUDENT</td>\n",
802
+ " <td>Sakir</td>\n",
803
+ " <td>20</td>\n",
804
+ " </tr>\n",
805
+ " <tr>\n",
806
+ " <th>21</th>\n",
807
+ " <td>104</td>\n",
808
+ " <td>9</td>\n",
809
+ " <td>I-NAME_STUDENT</td>\n",
810
+ " <td>Ahmad</td>\n",
811
+ " <td>21</td>\n",
812
+ " </tr>\n",
813
+ " <tr>\n",
814
+ " <th>22</th>\n",
815
+ " <td>112</td>\n",
816
+ " <td>5</td>\n",
817
+ " <td>B-NAME_STUDENT</td>\n",
818
+ " <td>Francisco</td>\n",
819
+ " <td>22</td>\n",
820
+ " </tr>\n",
821
+ " <tr>\n",
822
+ " <th>23</th>\n",
823
+ " <td>112</td>\n",
824
+ " <td>6</td>\n",
825
+ " <td>I-NAME_STUDENT</td>\n",
826
+ " <td>Ferreira</td>\n",
827
+ " <td>23</td>\n",
828
+ " </tr>\n",
829
+ " <tr>\n",
830
+ " <th>24</th>\n",
831
+ " <td>123</td>\n",
832
+ " <td>32</td>\n",
833
+ " <td>B-NAME_STUDENT</td>\n",
834
+ " <td>Stefano</td>\n",
835
+ " <td>24</td>\n",
836
+ " </tr>\n",
837
+ " <tr>\n",
838
+ " <th>25</th>\n",
839
+ " <td>123</td>\n",
840
+ " <td>33</td>\n",
841
+ " <td>I-NAME_STUDENT</td>\n",
842
+ " <td>Lovato</td>\n",
843
+ " <td>25</td>\n",
844
+ " </tr>\n",
845
+ " </tbody>\n",
846
+ "</table>\n",
847
+ "</div>"
848
+ ],
849
+ "text/plain": [
850
+ " document token label token_str row_id\n",
851
+ "0 7 9 B-NAME_STUDENT Nathalie 0\n",
852
+ "1 7 10 I-NAME_STUDENT Sylla 1\n",
853
+ "2 7 482 B-NAME_STUDENT Nathalie 2\n",
854
+ "3 7 483 I-NAME_STUDENT Sylla 3\n",
855
+ "4 7 741 B-NAME_STUDENT Nathalie 4\n",
856
+ "5 7 742 I-NAME_STUDENT Sylla 5\n",
857
+ "6 10 0 B-NAME_STUDENT Diego 6\n",
858
+ "7 10 1 I-NAME_STUDENT Estrada 7\n",
859
+ "8 10 464 B-NAME_STUDENT Diego 8\n",
860
+ "9 10 465 I-NAME_STUDENT Estrada 9\n",
861
+ "10 16 4 B-NAME_STUDENT Gilberto 10\n",
862
+ "11 16 5 I-NAME_STUDENT Gamboa 11\n",
863
+ "12 20 5 B-NAME_STUDENT Sindy 12\n",
864
+ "13 20 6 I-NAME_STUDENT Samaca 13\n",
865
+ "14 56 12 B-NAME_STUDENT Nadine 14\n",
866
+ "15 56 13 I-NAME_STUDENT Born 15\n",
867
+ "16 86 6 B-NAME_STUDENT Eladio 16\n",
868
+ "17 86 7 I-NAME_STUDENT Amaya 17\n",
869
+ "18 93 0 B-NAME_STUDENT Silvia 18\n",
870
+ "19 93 1 I-NAME_STUDENT Villalobos 19\n",
871
+ "20 104 8 B-NAME_STUDENT Sakir 20\n",
872
+ "21 104 9 I-NAME_STUDENT Ahmad 21\n",
873
+ "22 112 5 B-NAME_STUDENT Francisco 22\n",
874
+ "23 112 6 I-NAME_STUDENT Ferreira 23\n",
875
+ "24 123 32 B-NAME_STUDENT Stefano 24\n",
876
+ "25 123 33 I-NAME_STUDENT Lovato 25"
877
+ ]
878
+ },
879
+ "metadata": {},
880
+ "output_type": "display_data"
881
+ }
882
+ ],
883
+ "source": [
884
+ "df = pd.DataFrame(processed)\n",
885
+ "df[\"row_id\"] = list(range(len(df)))\n",
886
+ "display(df.head(100))"
887
+ ]
888
+ },
889
+ {
890
+ "cell_type": "code",
891
+ "execution_count": 13,
892
+ "id": "e3c5b306",
893
+ "metadata": {
894
+ "execution": {
895
+ "iopub.execute_input": "2025-04-30T15:28:29.274581Z",
896
+ "iopub.status.busy": "2025-04-30T15:28:29.273853Z",
897
+ "iopub.status.idle": "2025-04-30T15:28:29.287091Z",
898
+ "shell.execute_reply": "2025-04-30T15:28:29.286356Z"
899
+ },
900
+ "papermill": {
901
+ "duration": 0.01995,
902
+ "end_time": "2025-04-30T15:28:29.288780",
903
+ "exception": false,
904
+ "start_time": "2025-04-30T15:28:29.268830",
905
+ "status": "completed"
906
+ },
907
+ "tags": []
908
+ },
909
+ "outputs": [],
910
+ "source": [
911
+ "df[[\"row_id\", \"document\", \"token\", \"label\"]].to_csv(\"submission.csv\", index=False)"
912
+ ]
913
+ },
914
+ {
915
+ "cell_type": "code",
916
+ "execution_count": null,
917
+ "id": "493a9eb6",
918
+ "metadata": {
919
+ "papermill": {
920
+ "duration": 0.003789,
921
+ "end_time": "2025-04-30T15:28:29.297088",
922
+ "exception": false,
923
+ "start_time": "2025-04-30T15:28:29.293299",
924
+ "status": "completed"
925
+ },
926
+ "tags": []
927
+ },
928
+ "outputs": [],
929
+ "source": []
930
+ }
931
+ ],
932
+ "metadata": {
933
+ "kaggle": {
934
+ "accelerator": "gpu",
935
+ "dataSources": [
936
+ {
937
+ "databundleVersionId": 7500999,
938
+ "sourceId": 66653,
939
+ "sourceType": "competition"
940
+ },
941
+ {
942
+ "datasetId": 4626838,
943
+ "sourceId": 7882793,
944
+ "sourceType": "datasetVersion"
945
+ }
946
+ ],
947
+ "dockerImageVersionId": 30665,
948
+ "isGpuEnabled": true,
949
+ "isInternetEnabled": false,
950
+ "language": "python",
951
+ "sourceType": "notebook"
952
+ },
953
+ "kernelspec": {
954
+ "display_name": "Python 3",
955
+ "language": "python",
956
+ "name": "python3"
957
+ },
958
+ "language_info": {
959
+ "codemirror_mode": {
960
+ "name": "ipython",
961
+ "version": 3
962
+ },
963
+ "file_extension": ".py",
964
+ "mimetype": "text/x-python",
965
+ "name": "python",
966
+ "nbconvert_exporter": "python",
967
+ "pygments_lexer": "ipython3",
968
+ "version": "3.10.13"
969
+ },
970
+ "papermill": {
971
+ "default_parameters": {},
972
+ "duration": 60.15036,
973
+ "end_time": "2025-04-30T15:28:32.295763",
974
+ "environment_variables": {},
975
+ "exception": null,
976
+ "input_path": "__notebook__.ipynb",
977
+ "output_path": "__notebook__.ipynb",
978
+ "parameters": {},
979
+ "start_time": "2025-04-30T15:27:32.145403",
980
+ "version": "2.5.0"
981
+ },
982
+ "widgets": {
983
+ "application/vnd.jupyter.widget-state+json": {
984
+ "state": {
985
+ "06a9c6b88ead4f49a96be340378209a1": {
986
+ "model_module": "@jupyter-widgets/controls",
987
+ "model_module_version": "1.5.0",
988
+ "model_name": "ProgressStyleModel",
989
+ "state": {
990
+ "_model_module": "@jupyter-widgets/controls",
991
+ "_model_module_version": "1.5.0",
992
+ "_model_name": "ProgressStyleModel",
993
+ "_view_count": null,
994
+ "_view_module": "@jupyter-widgets/base",
995
+ "_view_module_version": "1.2.0",
996
+ "_view_name": "StyleView",
997
+ "bar_color": null,
998
+ "description_width": ""
999
+ }
1000
+ },
1001
+ "131b74fe075f4174b6362f1d1d8d6c18": {
1002
+ "model_module": "@jupyter-widgets/base",
1003
+ "model_module_version": "1.2.0",
1004
+ "model_name": "LayoutModel",
1005
+ "state": {
1006
+ "_model_module": "@jupyter-widgets/base",
1007
+ "_model_module_version": "1.2.0",
1008
+ "_model_name": "LayoutModel",
1009
+ "_view_count": null,
1010
+ "_view_module": "@jupyter-widgets/base",
1011
+ "_view_module_version": "1.2.0",
1012
+ "_view_name": "LayoutView",
1013
+ "align_content": null,
1014
+ "align_items": null,
1015
+ "align_self": null,
1016
+ "border": null,
1017
+ "bottom": null,
1018
+ "display": null,
1019
+ "flex": null,
1020
+ "flex_flow": null,
1021
+ "grid_area": null,
1022
+ "grid_auto_columns": null,
1023
+ "grid_auto_flow": null,
1024
+ "grid_auto_rows": null,
1025
+ "grid_column": null,
1026
+ "grid_gap": null,
1027
+ "grid_row": null,
1028
+ "grid_template_areas": null,
1029
+ "grid_template_columns": null,
1030
+ "grid_template_rows": null,
1031
+ "height": null,
1032
+ "justify_content": null,
1033
+ "justify_items": null,
1034
+ "left": null,
1035
+ "margin": null,
1036
+ "max_height": null,
1037
+ "max_width": null,
1038
+ "min_height": null,
1039
+ "min_width": null,
1040
+ "object_fit": null,
1041
+ "object_position": null,
1042
+ "order": null,
1043
+ "overflow": null,
1044
+ "overflow_x": null,
1045
+ "overflow_y": null,
1046
+ "padding": null,
1047
+ "right": null,
1048
+ "top": null,
1049
+ "visibility": null,
1050
+ "width": null
1051
+ }
1052
+ },
1053
+ "1a471d08224e42b095b0283f8c3dd9e9": {
1054
+ "model_module": "@jupyter-widgets/base",
1055
+ "model_module_version": "1.2.0",
1056
+ "model_name": "LayoutModel",
1057
+ "state": {
1058
+ "_model_module": "@jupyter-widgets/base",
1059
+ "_model_module_version": "1.2.0",
1060
+ "_model_name": "LayoutModel",
1061
+ "_view_count": null,
1062
+ "_view_module": "@jupyter-widgets/base",
1063
+ "_view_module_version": "1.2.0",
1064
+ "_view_name": "LayoutView",
1065
+ "align_content": null,
1066
+ "align_items": null,
1067
+ "align_self": null,
1068
+ "border": null,
1069
+ "bottom": null,
1070
+ "display": null,
1071
+ "flex": null,
1072
+ "flex_flow": null,
1073
+ "grid_area": null,
1074
+ "grid_auto_columns": null,
1075
+ "grid_auto_flow": null,
1076
+ "grid_auto_rows": null,
1077
+ "grid_column": null,
1078
+ "grid_gap": null,
1079
+ "grid_row": null,
1080
+ "grid_template_areas": null,
1081
+ "grid_template_columns": null,
1082
+ "grid_template_rows": null,
1083
+ "height": null,
1084
+ "justify_content": null,
1085
+ "justify_items": null,
1086
+ "left": null,
1087
+ "margin": null,
1088
+ "max_height": null,
1089
+ "max_width": null,
1090
+ "min_height": null,
1091
+ "min_width": null,
1092
+ "object_fit": null,
1093
+ "object_position": null,
1094
+ "order": null,
1095
+ "overflow": null,
1096
+ "overflow_x": null,
1097
+ "overflow_y": null,
1098
+ "padding": null,
1099
+ "right": null,
1100
+ "top": null,
1101
+ "visibility": null,
1102
+ "width": null
1103
+ }
1104
+ },
1105
+ "2767366afecc4147be4dfcabbe18f673": {
1106
+ "model_module": "@jupyter-widgets/base",
1107
+ "model_module_version": "1.2.0",
1108
+ "model_name": "LayoutModel",
1109
+ "state": {
1110
+ "_model_module": "@jupyter-widgets/base",
1111
+ "_model_module_version": "1.2.0",
1112
+ "_model_name": "LayoutModel",
1113
+ "_view_count": null,
1114
+ "_view_module": "@jupyter-widgets/base",
1115
+ "_view_module_version": "1.2.0",
1116
+ "_view_name": "LayoutView",
1117
+ "align_content": null,
1118
+ "align_items": null,
1119
+ "align_self": null,
1120
+ "border": null,
1121
+ "bottom": null,
1122
+ "display": null,
1123
+ "flex": null,
1124
+ "flex_flow": null,
1125
+ "grid_area": null,
1126
+ "grid_auto_columns": null,
1127
+ "grid_auto_flow": null,
1128
+ "grid_auto_rows": null,
1129
+ "grid_column": null,
1130
+ "grid_gap": null,
1131
+ "grid_row": null,
1132
+ "grid_template_areas": null,
1133
+ "grid_template_columns": null,
1134
+ "grid_template_rows": null,
1135
+ "height": null,
1136
+ "justify_content": null,
1137
+ "justify_items": null,
1138
+ "left": null,
1139
+ "margin": null,
1140
+ "max_height": null,
1141
+ "max_width": null,
1142
+ "min_height": null,
1143
+ "min_width": null,
1144
+ "object_fit": null,
1145
+ "object_position": null,
1146
+ "order": null,
1147
+ "overflow": null,
1148
+ "overflow_x": null,
1149
+ "overflow_y": null,
1150
+ "padding": null,
1151
+ "right": null,
1152
+ "top": null,
1153
+ "visibility": null,
1154
+ "width": null
1155
+ }
1156
+ },
1157
+ "281fad7ccc0949d9b376b9bc51a5a37c": {
1158
+ "model_module": "@jupyter-widgets/controls",
1159
+ "model_module_version": "1.5.0",
1160
+ "model_name": "ProgressStyleModel",
1161
+ "state": {
1162
+ "_model_module": "@jupyter-widgets/controls",
1163
+ "_model_module_version": "1.5.0",
1164
+ "_model_name": "ProgressStyleModel",
1165
+ "_view_count": null,
1166
+ "_view_module": "@jupyter-widgets/base",
1167
+ "_view_module_version": "1.2.0",
1168
+ "_view_name": "StyleView",
1169
+ "bar_color": null,
1170
+ "description_width": ""
1171
+ }
1172
+ },
1173
+ "35b69818cee842c1bcdb21fa19a0df48": {
1174
+ "model_module": "@jupyter-widgets/controls",
1175
+ "model_module_version": "1.5.0",
1176
+ "model_name": "FloatProgressModel",
1177
+ "state": {
1178
+ "_dom_classes": [],
1179
+ "_model_module": "@jupyter-widgets/controls",
1180
+ "_model_module_version": "1.5.0",
1181
+ "_model_name": "FloatProgressModel",
1182
+ "_view_count": null,
1183
+ "_view_module": "@jupyter-widgets/controls",
1184
+ "_view_module_version": "1.5.0",
1185
+ "_view_name": "ProgressView",
1186
+ "bar_style": "success",
1187
+ "description": "",
1188
+ "description_tooltip": null,
1189
+ "layout": "IPY_MODEL_2767366afecc4147be4dfcabbe18f673",
1190
+ "max": 5.0,
1191
+ "min": 0.0,
1192
+ "orientation": "horizontal",
1193
+ "style": "IPY_MODEL_06a9c6b88ead4f49a96be340378209a1",
1194
+ "value": 5.0
1195
+ }
1196
+ },
1197
+ "41df8caea2b9469dbf1adc1b5e056087": {
1198
+ "model_module": "@jupyter-widgets/controls",
1199
+ "model_module_version": "1.5.0",
1200
+ "model_name": "DescriptionStyleModel",
1201
+ "state": {
1202
+ "_model_module": "@jupyter-widgets/controls",
1203
+ "_model_module_version": "1.5.0",
1204
+ "_model_name": "DescriptionStyleModel",
1205
+ "_view_count": null,
1206
+ "_view_module": "@jupyter-widgets/base",
1207
+ "_view_module_version": "1.2.0",
1208
+ "_view_name": "StyleView",
1209
+ "description_width": ""
1210
+ }
1211
+ },
1212
+ "5512699220b84dbe87b51b77be6ac346": {
1213
+ "model_module": "@jupyter-widgets/base",
1214
+ "model_module_version": "1.2.0",
1215
+ "model_name": "LayoutModel",
1216
+ "state": {
1217
+ "_model_module": "@jupyter-widgets/base",
1218
+ "_model_module_version": "1.2.0",
1219
+ "_model_name": "LayoutModel",
1220
+ "_view_count": null,
1221
+ "_view_module": "@jupyter-widgets/base",
1222
+ "_view_module_version": "1.2.0",
1223
+ "_view_name": "LayoutView",
1224
+ "align_content": null,
1225
+ "align_items": null,
1226
+ "align_self": null,
1227
+ "border": null,
1228
+ "bottom": null,
1229
+ "display": null,
1230
+ "flex": null,
1231
+ "flex_flow": null,
1232
+ "grid_area": null,
1233
+ "grid_auto_columns": null,
1234
+ "grid_auto_flow": null,
1235
+ "grid_auto_rows": null,
1236
+ "grid_column": null,
1237
+ "grid_gap": null,
1238
+ "grid_row": null,
1239
+ "grid_template_areas": null,
1240
+ "grid_template_columns": null,
1241
+ "grid_template_rows": null,
1242
+ "height": null,
1243
+ "justify_content": null,
1244
+ "justify_items": null,
1245
+ "left": null,
1246
+ "margin": null,
1247
+ "max_height": null,
1248
+ "max_width": null,
1249
+ "min_height": null,
1250
+ "min_width": null,
1251
+ "object_fit": null,
1252
+ "object_position": null,
1253
+ "order": null,
1254
+ "overflow": null,
1255
+ "overflow_x": null,
1256
+ "overflow_y": null,
1257
+ "padding": null,
1258
+ "right": null,
1259
+ "top": null,
1260
+ "visibility": null,
1261
+ "width": null
1262
+ }
1263
+ },
1264
+ "5951514148bd4f938c2559866f91be40": {
1265
+ "model_module": "@jupyter-widgets/controls",
1266
+ "model_module_version": "1.5.0",
1267
+ "model_name": "HBoxModel",
1268
+ "state": {
1269
+ "_dom_classes": [],
1270
+ "_model_module": "@jupyter-widgets/controls",
1271
+ "_model_module_version": "1.5.0",
1272
+ "_model_name": "HBoxModel",
1273
+ "_view_count": null,
1274
+ "_view_module": "@jupyter-widgets/controls",
1275
+ "_view_module_version": "1.5.0",
1276
+ "_view_name": "HBoxView",
1277
+ "box_style": "",
1278
+ "children": [
1279
+ "IPY_MODEL_680ee12021f24e88969f766421390554",
1280
+ "IPY_MODEL_35b69818cee842c1bcdb21fa19a0df48",
1281
+ "IPY_MODEL_dcf6b07ee9cb4f16b314dbe42a5e7abe"
1282
+ ],
1283
+ "layout": "IPY_MODEL_1a471d08224e42b095b0283f8c3dd9e9"
1284
+ }
1285
+ },
1286
+ "59596944958e440695e4ba4f96570ada": {
1287
+ "model_module": "@jupyter-widgets/controls",
1288
+ "model_module_version": "1.5.0",
1289
+ "model_name": "HTMLModel",
1290
+ "state": {
1291
+ "_dom_classes": [],
1292
+ "_model_module": "@jupyter-widgets/controls",
1293
+ "_model_module_version": "1.5.0",
1294
+ "_model_name": "HTMLModel",
1295
+ "_view_count": null,
1296
+ "_view_module": "@jupyter-widgets/controls",
1297
+ "_view_module_version": "1.5.0",
1298
+ "_view_name": "HTMLView",
1299
+ "description": "",
1300
+ "description_tooltip": null,
1301
+ "layout": "IPY_MODEL_74ff34caa9cb4410ad25cb99dbccb46f",
1302
+ "placeholder": "​",
1303
+ "style": "IPY_MODEL_41df8caea2b9469dbf1adc1b5e056087",
1304
+ "value": "#1: 100%"
1305
+ }
1306
+ },
1307
+ "6735ff3730f74fed8b055a7f19afd508": {
1308
+ "model_module": "@jupyter-widgets/controls",
1309
+ "model_module_version": "1.5.0",
1310
+ "model_name": "DescriptionStyleModel",
1311
+ "state": {
1312
+ "_model_module": "@jupyter-widgets/controls",
1313
+ "_model_module_version": "1.5.0",
1314
+ "_model_name": "DescriptionStyleModel",
1315
+ "_view_count": null,
1316
+ "_view_module": "@jupyter-widgets/base",
1317
+ "_view_module_version": "1.2.0",
1318
+ "_view_name": "StyleView",
1319
+ "description_width": ""
1320
+ }
1321
+ },
1322
+ "680ee12021f24e88969f766421390554": {
1323
+ "model_module": "@jupyter-widgets/controls",
1324
+ "model_module_version": "1.5.0",
1325
+ "model_name": "HTMLModel",
1326
+ "state": {
1327
+ "_dom_classes": [],
1328
+ "_model_module": "@jupyter-widgets/controls",
1329
+ "_model_module_version": "1.5.0",
1330
+ "_model_name": "HTMLModel",
1331
+ "_view_count": null,
1332
+ "_view_module": "@jupyter-widgets/controls",
1333
+ "_view_module_version": "1.5.0",
1334
+ "_view_name": "HTMLView",
1335
+ "description": "",
1336
+ "description_tooltip": null,
1337
+ "layout": "IPY_MODEL_ac3981001ad041e997dbc05dad677c06",
1338
+ "placeholder": "​",
1339
+ "style": "IPY_MODEL_d2c93cda9bed48ccb273911aaf36048a",
1340
+ "value": "#0: 100%"
1341
+ }
1342
+ },
1343
+ "6fe5d4d95b844ceba24521a27090e7ae": {
1344
+ "model_module": "@jupyter-widgets/controls",
1345
+ "model_module_version": "1.5.0",
1346
+ "model_name": "DescriptionStyleModel",
1347
+ "state": {
1348
+ "_model_module": "@jupyter-widgets/controls",
1349
+ "_model_module_version": "1.5.0",
1350
+ "_model_name": "DescriptionStyleModel",
1351
+ "_view_count": null,
1352
+ "_view_module": "@jupyter-widgets/base",
1353
+ "_view_module_version": "1.2.0",
1354
+ "_view_name": "StyleView",
1355
+ "description_width": ""
1356
+ }
1357
+ },
1358
+ "74ff34caa9cb4410ad25cb99dbccb46f": {
1359
+ "model_module": "@jupyter-widgets/base",
1360
+ "model_module_version": "1.2.0",
1361
+ "model_name": "LayoutModel",
1362
+ "state": {
1363
+ "_model_module": "@jupyter-widgets/base",
1364
+ "_model_module_version": "1.2.0",
1365
+ "_model_name": "LayoutModel",
1366
+ "_view_count": null,
1367
+ "_view_module": "@jupyter-widgets/base",
1368
+ "_view_module_version": "1.2.0",
1369
+ "_view_name": "LayoutView",
1370
+ "align_content": null,
1371
+ "align_items": null,
1372
+ "align_self": null,
1373
+ "border": null,
1374
+ "bottom": null,
1375
+ "display": null,
1376
+ "flex": null,
1377
+ "flex_flow": null,
1378
+ "grid_area": null,
1379
+ "grid_auto_columns": null,
1380
+ "grid_auto_flow": null,
1381
+ "grid_auto_rows": null,
1382
+ "grid_column": null,
1383
+ "grid_gap": null,
1384
+ "grid_row": null,
1385
+ "grid_template_areas": null,
1386
+ "grid_template_columns": null,
1387
+ "grid_template_rows": null,
1388
+ "height": null,
1389
+ "justify_content": null,
1390
+ "justify_items": null,
1391
+ "left": null,
1392
+ "margin": null,
1393
+ "max_height": null,
1394
+ "max_width": null,
1395
+ "min_height": null,
1396
+ "min_width": null,
1397
+ "object_fit": null,
1398
+ "object_position": null,
1399
+ "order": null,
1400
+ "overflow": null,
1401
+ "overflow_x": null,
1402
+ "overflow_y": null,
1403
+ "padding": null,
1404
+ "right": null,
1405
+ "top": null,
1406
+ "visibility": null,
1407
+ "width": null
1408
+ }
1409
+ },
1410
+ "87d695ef5aea48a882031c27f85a3752": {
1411
+ "model_module": "@jupyter-widgets/controls",
1412
+ "model_module_version": "1.5.0",
1413
+ "model_name": "FloatProgressModel",
1414
+ "state": {
1415
+ "_dom_classes": [],
1416
+ "_model_module": "@jupyter-widgets/controls",
1417
+ "_model_module_version": "1.5.0",
1418
+ "_model_name": "FloatProgressModel",
1419
+ "_view_count": null,
1420
+ "_view_module": "@jupyter-widgets/controls",
1421
+ "_view_module_version": "1.5.0",
1422
+ "_view_name": "ProgressView",
1423
+ "bar_style": "success",
1424
+ "description": "",
1425
+ "description_tooltip": null,
1426
+ "layout": "IPY_MODEL_131b74fe075f4174b6362f1d1d8d6c18",
1427
+ "max": 5.0,
1428
+ "min": 0.0,
1429
+ "orientation": "horizontal",
1430
+ "style": "IPY_MODEL_281fad7ccc0949d9b376b9bc51a5a37c",
1431
+ "value": 5.0
1432
+ }
1433
+ },
1434
+ "88430d270ddf4976af32c09d1afb0ad8": {
1435
+ "model_module": "@jupyter-widgets/controls",
1436
+ "model_module_version": "1.5.0",
1437
+ "model_name": "HBoxModel",
1438
+ "state": {
1439
+ "_dom_classes": [],
1440
+ "_model_module": "@jupyter-widgets/controls",
1441
+ "_model_module_version": "1.5.0",
1442
+ "_model_name": "HBoxModel",
1443
+ "_view_count": null,
1444
+ "_view_module": "@jupyter-widgets/controls",
1445
+ "_view_module_version": "1.5.0",
1446
+ "_view_name": "HBoxView",
1447
+ "box_style": "",
1448
+ "children": [
1449
+ "IPY_MODEL_59596944958e440695e4ba4f96570ada",
1450
+ "IPY_MODEL_87d695ef5aea48a882031c27f85a3752",
1451
+ "IPY_MODEL_d79bec3cedfb4ded91d9d164a266ea3c"
1452
+ ],
1453
+ "layout": "IPY_MODEL_5512699220b84dbe87b51b77be6ac346"
1454
+ }
1455
+ },
1456
+ "aa22d45919ae45e4852612397edf8e6b": {
1457
+ "model_module": "@jupyter-widgets/base",
1458
+ "model_module_version": "1.2.0",
1459
+ "model_name": "LayoutModel",
1460
+ "state": {
1461
+ "_model_module": "@jupyter-widgets/base",
1462
+ "_model_module_version": "1.2.0",
1463
+ "_model_name": "LayoutModel",
1464
+ "_view_count": null,
1465
+ "_view_module": "@jupyter-widgets/base",
1466
+ "_view_module_version": "1.2.0",
1467
+ "_view_name": "LayoutView",
1468
+ "align_content": null,
1469
+ "align_items": null,
1470
+ "align_self": null,
1471
+ "border": null,
1472
+ "bottom": null,
1473
+ "display": null,
1474
+ "flex": null,
1475
+ "flex_flow": null,
1476
+ "grid_area": null,
1477
+ "grid_auto_columns": null,
1478
+ "grid_auto_flow": null,
1479
+ "grid_auto_rows": null,
1480
+ "grid_column": null,
1481
+ "grid_gap": null,
1482
+ "grid_row": null,
1483
+ "grid_template_areas": null,
1484
+ "grid_template_columns": null,
1485
+ "grid_template_rows": null,
1486
+ "height": null,
1487
+ "justify_content": null,
1488
+ "justify_items": null,
1489
+ "left": null,
1490
+ "margin": null,
1491
+ "max_height": null,
1492
+ "max_width": null,
1493
+ "min_height": null,
1494
+ "min_width": null,
1495
+ "object_fit": null,
1496
+ "object_position": null,
1497
+ "order": null,
1498
+ "overflow": null,
1499
+ "overflow_x": null,
1500
+ "overflow_y": null,
1501
+ "padding": null,
1502
+ "right": null,
1503
+ "top": null,
1504
+ "visibility": null,
1505
+ "width": null
1506
+ }
1507
+ },
1508
+ "ac3981001ad041e997dbc05dad677c06": {
1509
+ "model_module": "@jupyter-widgets/base",
1510
+ "model_module_version": "1.2.0",
1511
+ "model_name": "LayoutModel",
1512
+ "state": {
1513
+ "_model_module": "@jupyter-widgets/base",
1514
+ "_model_module_version": "1.2.0",
1515
+ "_model_name": "LayoutModel",
1516
+ "_view_count": null,
1517
+ "_view_module": "@jupyter-widgets/base",
1518
+ "_view_module_version": "1.2.0",
1519
+ "_view_name": "LayoutView",
1520
+ "align_content": null,
1521
+ "align_items": null,
1522
+ "align_self": null,
1523
+ "border": null,
1524
+ "bottom": null,
1525
+ "display": null,
1526
+ "flex": null,
1527
+ "flex_flow": null,
1528
+ "grid_area": null,
1529
+ "grid_auto_columns": null,
1530
+ "grid_auto_flow": null,
1531
+ "grid_auto_rows": null,
1532
+ "grid_column": null,
1533
+ "grid_gap": null,
1534
+ "grid_row": null,
1535
+ "grid_template_areas": null,
1536
+ "grid_template_columns": null,
1537
+ "grid_template_rows": null,
1538
+ "height": null,
1539
+ "justify_content": null,
1540
+ "justify_items": null,
1541
+ "left": null,
1542
+ "margin": null,
1543
+ "max_height": null,
1544
+ "max_width": null,
1545
+ "min_height": null,
1546
+ "min_width": null,
1547
+ "object_fit": null,
1548
+ "object_position": null,
1549
+ "order": null,
1550
+ "overflow": null,
1551
+ "overflow_x": null,
1552
+ "overflow_y": null,
1553
+ "padding": null,
1554
+ "right": null,
1555
+ "top": null,
1556
+ "visibility": null,
1557
+ "width": null
1558
+ }
1559
+ },
1560
+ "b7789187719a4774a9d31cb1f5b82884": {
1561
+ "model_module": "@jupyter-widgets/base",
1562
+ "model_module_version": "1.2.0",
1563
+ "model_name": "LayoutModel",
1564
+ "state": {
1565
+ "_model_module": "@jupyter-widgets/base",
1566
+ "_model_module_version": "1.2.0",
1567
+ "_model_name": "LayoutModel",
1568
+ "_view_count": null,
1569
+ "_view_module": "@jupyter-widgets/base",
1570
+ "_view_module_version": "1.2.0",
1571
+ "_view_name": "LayoutView",
1572
+ "align_content": null,
1573
+ "align_items": null,
1574
+ "align_self": null,
1575
+ "border": null,
1576
+ "bottom": null,
1577
+ "display": null,
1578
+ "flex": null,
1579
+ "flex_flow": null,
1580
+ "grid_area": null,
1581
+ "grid_auto_columns": null,
1582
+ "grid_auto_flow": null,
1583
+ "grid_auto_rows": null,
1584
+ "grid_column": null,
1585
+ "grid_gap": null,
1586
+ "grid_row": null,
1587
+ "grid_template_areas": null,
1588
+ "grid_template_columns": null,
1589
+ "grid_template_rows": null,
1590
+ "height": null,
1591
+ "justify_content": null,
1592
+ "justify_items": null,
1593
+ "left": null,
1594
+ "margin": null,
1595
+ "max_height": null,
1596
+ "max_width": null,
1597
+ "min_height": null,
1598
+ "min_width": null,
1599
+ "object_fit": null,
1600
+ "object_position": null,
1601
+ "order": null,
1602
+ "overflow": null,
1603
+ "overflow_x": null,
1604
+ "overflow_y": null,
1605
+ "padding": null,
1606
+ "right": null,
1607
+ "top": null,
1608
+ "visibility": null,
1609
+ "width": null
1610
+ }
1611
+ },
1612
+ "d2c93cda9bed48ccb273911aaf36048a": {
1613
+ "model_module": "@jupyter-widgets/controls",
1614
+ "model_module_version": "1.5.0",
1615
+ "model_name": "DescriptionStyleModel",
1616
+ "state": {
1617
+ "_model_module": "@jupyter-widgets/controls",
1618
+ "_model_module_version": "1.5.0",
1619
+ "_model_name": "DescriptionStyleModel",
1620
+ "_view_count": null,
1621
+ "_view_module": "@jupyter-widgets/base",
1622
+ "_view_module_version": "1.2.0",
1623
+ "_view_name": "StyleView",
1624
+ "description_width": ""
1625
+ }
1626
+ },
1627
+ "d79bec3cedfb4ded91d9d164a266ea3c": {
1628
+ "model_module": "@jupyter-widgets/controls",
1629
+ "model_module_version": "1.5.0",
1630
+ "model_name": "HTMLModel",
1631
+ "state": {
1632
+ "_dom_classes": [],
1633
+ "_model_module": "@jupyter-widgets/controls",
1634
+ "_model_module_version": "1.5.0",
1635
+ "_model_name": "HTMLModel",
1636
+ "_view_count": null,
1637
+ "_view_module": "@jupyter-widgets/controls",
1638
+ "_view_module_version": "1.5.0",
1639
+ "_view_name": "HTMLView",
1640
+ "description": "",
1641
+ "description_tooltip": null,
1642
+ "layout": "IPY_MODEL_aa22d45919ae45e4852612397edf8e6b",
1643
+ "placeholder": "​",
1644
+ "style": "IPY_MODEL_6735ff3730f74fed8b055a7f19afd508",
1645
+ "value": " 5/5 [00:00&lt;00:00, 82.52ex/s]"
1646
+ }
1647
+ },
1648
+ "dcf6b07ee9cb4f16b314dbe42a5e7abe": {
1649
+ "model_module": "@jupyter-widgets/controls",
1650
+ "model_module_version": "1.5.0",
1651
+ "model_name": "HTMLModel",
1652
+ "state": {
1653
+ "_dom_classes": [],
1654
+ "_model_module": "@jupyter-widgets/controls",
1655
+ "_model_module_version": "1.5.0",
1656
+ "_model_name": "HTMLModel",
1657
+ "_view_count": null,
1658
+ "_view_module": "@jupyter-widgets/controls",
1659
+ "_view_module_version": "1.5.0",
1660
+ "_view_name": "HTMLView",
1661
+ "description": "",
1662
+ "description_tooltip": null,
1663
+ "layout": "IPY_MODEL_b7789187719a4774a9d31cb1f5b82884",
1664
+ "placeholder": "​",
1665
+ "style": "IPY_MODEL_6fe5d4d95b844ceba24521a27090e7ae",
1666
+ "value": " 5/5 [00:00&lt;00:00, 59.91ex/s]"
1667
+ }
1668
+ }
1669
+ },
1670
+ "version_major": 2,
1671
+ "version_minor": 0
1672
+ }
1673
+ }
1674
+ },
1675
+ "nbformat": 4,
1676
+ "nbformat_minor": 5
1677
+ }
code/inference-single-model-no-pp-privatelb-0-963.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # %%
3
+ import re
4
+ import os
5
+ import json
6
+ import torch
7
+ import argparse
8
+ from tqdm import tqdm
9
+ from pathlib import Path
10
+ from itertools import chain
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification
15
+ from datasets import Dataset
16
+ from spacy.lang.en import English
17
+
18
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
19
+
20
+ USE_SIMPLIFIED_LABELS_INFERENCE = True # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS FOR INFERENCE
21
+
22
+ # %%
23
+ backbone = 'microsoft/deberta-v3-large'
24
+
25
+
26
+ model_dir_for_inference = '/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo_simplified_labels_fold0_best_hf'
27
+
28
+
29
+ # th = 0.5 # Threshold for 'O' class - replaced by 'O' scaling
30
+ o_class_scaler = 0.1 # Factor to scale down 'O' class probability
31
+
32
+ # Define label sets based on the flag
33
+ if USE_SIMPLIFIED_LABELS_INFERENCE:
34
+ pii_types_active = [
35
+ 'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
36
+ 'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
37
+ ]
38
+ all_labels_active_inf = sorted(pii_types_active + ['O'])
39
+ else:
40
+ # Original BIO labels (ensure this matches the set used in training if USE_SIMPLIFIED_LABELS was False)
41
+ original_target_bio_inf = sorted([
42
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
43
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
44
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
45
+ 'O'
46
+ ])
47
+ all_labels_active_inf = original_target_bio_inf
48
+
49
+ id2label_active_inf = {i: l for i, l in enumerate(all_labels_active_inf)}
50
+ # Determine O_LABEL_INDEX for thresholding
51
+ O_LABEL_STR = "O"
52
+ O_LABEL_INDEX = -1
53
+ for i, label_str_val in id2label_active_inf.items():
54
+ if label_str_val == O_LABEL_STR:
55
+ O_LABEL_INDEX = i
56
+ break
57
+ if O_LABEL_INDEX == -1:
58
+ raise ValueError(f"Could not find '{O_LABEL_STR}' in id2label_active_inf: {id2label_active_inf}")
59
+
60
+ print(f"Using {'simplified' if USE_SIMPLIFIED_LABELS_INFERENCE else 'original BIO'} id2label for inference: {id2label_active_inf}")
61
+ print(f"Index for 'O' label is: {O_LABEL_INDEX}")
62
+
63
+
64
+ # %%
65
+ def tokenize(example, tokenizer):
66
+ text = []
67
+ token_map = []
68
+
69
+ idx = 0
70
+
71
+ for t, ws in zip(example["tokens"], example["trailing_whitespace"]):
72
+
73
+ text.append(t)
74
+ token_map.extend([idx]*len(t))
75
+ if ws:
76
+ text.append(" ")
77
+ token_map.append(-1)
78
+
79
+ idx += 1
80
+
81
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
82
+
83
+
84
+ return {
85
+ **tokenized,
86
+ "token_map": token_map,
87
+ }
88
+
89
+ # %%
90
+ with open("/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/test.json", "r") as f:
91
+ data = json.load(f)
92
+
93
+ ds = Dataset.from_dict({
94
+ "full_text": [x["full_text"] for x in data],
95
+ "document": [x["document"] for x in data],
96
+ "tokens": [x["tokens"] for x in data],
97
+ "trailing_whitespace": [x["trailing_whitespace"] for x in data],
98
+ })
99
+
100
+ tokenizer = AutoTokenizer.from_pretrained(model_dir_for_inference)
101
+ collator = DataCollatorForTokenClassification(tokenizer)
102
+ ds = ds.map(tokenize, fn_kwargs={"tokenizer": tokenizer}, num_proc=2)
103
+
104
+ # %%
105
+ ds[0].keys()
106
+
107
+ # %%
108
+
109
+ print(f"Loading model from: {model_dir_for_inference}")
110
+ model = AutoModelForTokenClassification.from_pretrained(model_dir_for_inference)
111
+ model.eval()
112
+ model.to(device)
113
+
114
+
115
+ # %%
116
+ preds_final_simplified_ids = []
117
+ with torch.no_grad():
118
+ for sample in tqdm(ds, desc="Inferencing"):
119
+ input_ids = torch.tensor([sample['input_ids']]).to(device)
120
+ attention_mask = torch.tensor([sample['attention_mask']]).to(device)
121
+
122
+ outputs = model(input_ids, attention_mask=attention_mask)
123
+ logits = outputs.logits # Shape (1, seq_len, num_active_labels)
124
+
125
+ all_probs = logits.softmax(dim=2) # Shape (1, seq_len, num_active_labels)
126
+
127
+ # scale down 'O' probability
128
+ all_probs_scaled = all_probs.clone()
129
+ if O_LABEL_INDEX != -1:
130
+ all_probs_scaled[:, :, O_LABEL_INDEX] *= o_class_scaler
131
+ else:
132
+ print("Warning: O_LABEL_INDEX not found, 'O' class scaling not applied.")
133
+
134
+ predicted_ids = torch.argmax(all_probs_scaled, dim=2).cpu().numpy()
135
+ preds_final_simplified_ids.append(predicted_ids[0]) # remove batch dim
136
+
137
+ # %%
138
+ # Store simplified predictions mapped to original tokens first
139
+ intermediate_processed = []
140
+ unique_token_preds = set() # To handle cases where multiple subwords map to the same original token
141
+
142
+ for pred_ids_for_doc, token_map_for_doc, offsets_for_doc, tokens_for_doc, doc_id in zip(
143
+ preds_final_simplified_ids, ds["token_map"], ds["offset_mapping"], ds["tokens"], ds["document"]
144
+ ):
145
+ for subword_pred_id, (start_char_idx, end_char_idx) in zip(pred_ids_for_doc, offsets_for_doc):
146
+ active_label_pred_str = id2label_active_inf[subword_pred_id]
147
+
148
+ if start_char_idx == end_char_idx == 0: # Skip special tokens like [CLS], [SEP] based on offset
149
+ continue
150
+
151
+ # Find the original token_id using the start_char_idx from offset_mapping
152
+ current_char_idx = start_char_idx
153
+ if current_char_idx < len(token_map_for_doc) and token_map_for_doc[current_char_idx] == -1 and current_char_idx + 1 < len(token_map_for_doc):
154
+ current_char_idx +=1
155
+
156
+ if current_char_idx >= len(token_map_for_doc):
157
+ continue
158
+
159
+ original_token_id = token_map_for_doc[current_char_idx]
160
+
161
+ # Ignore "O" predictions and invalid token_ids
162
+ if active_label_pred_str != "O" and original_token_id != -1:
163
+ token_text_content = tokens_for_doc[original_token_id].strip()
164
+ # Only proceed if the token is not empty after stripping whitespace
165
+ if token_text_content:
166
+ prediction_key = (doc_id, original_token_id)
167
+ if prediction_key not in unique_token_preds:
168
+ intermediate_processed.append({
169
+ "document": doc_id,
170
+ "token": original_token_id,
171
+ "label_pred": active_label_pred_str, # Store the predicted label
172
+ "token_str": tokens_for_doc[original_token_id] # Store original token
173
+ })
174
+ unique_token_preds.add(prediction_key)
175
+
176
+ # %%
177
+ df_intermediate = pd.DataFrame(intermediate_processed)
178
+
179
+ final_predictions_for_submission = []
180
+
181
+ if not df_intermediate.empty:
182
+ if USE_SIMPLIFIED_LABELS_INFERENCE:
183
+ # NAME_STUDENT capitalization check
184
+ name_student_mask = df_intermediate['label_pred'] == 'NAME_STUDENT'
185
+
186
+ def is_potentially_name_case(s):
187
+ if not s: return False
188
+ return (s[0].isalpha() and s[0].isupper()) or s.isupper()
189
+
190
+ not_name_case_mask = ~df_intermediate['token_str'].apply(is_potentially_name_case)
191
+ numeric_token_mask = df_intermediate['token_str'].apply(lambda x: x.isdigit())
192
+ rows_to_filter_out_indices = df_intermediate[name_student_mask & (not_name_case_mask | numeric_token_mask)].index
193
+
194
+ if not rows_to_filter_out_indices.empty:
195
+ print(f"Applying NAME_STUDENT capitalization/numeric heuristic: Filtering out {len(rows_to_filter_out_indices)} rows.")
196
+ df_intermediate = df_intermediate.drop(rows_to_filter_out_indices)
197
+
198
+ if not df_intermediate.empty:
199
+ df_intermediate = df_intermediate.sort_values(by=["document", "token"])
200
+
201
+ for doc_id, group in df_intermediate.groupby("document"):
202
+ sorted_group = group.sort_values("token")
203
+
204
+ previous_token_id = -1
205
+ previous_pii_type_simplified = None
206
+
207
+ for _, row in sorted_group.iterrows():
208
+ current_token_id = row["token"]
209
+ current_label_pred = row["label_pred"] #direct model output
210
+
211
+ final_label_to_submit = ""
212
+
213
+ if USE_SIMPLIFIED_LABELS_INFERENCE:
214
+ # Reconstruct BIO
215
+ # A new entity starts if:
216
+ # 1. It's the first PII token for the document.
217
+ # 2. The current token is not contiguous with the previous PII token.
218
+ # 3. The PII type changes.
219
+ if (previous_pii_type_simplified is None) or \
220
+ (current_token_id != previous_token_id + 1) or \
221
+ (previous_pii_type_simplified != current_label_pred): # current_label_pred is simplified here
222
+ bio_tag = "B-"
223
+ else:
224
+ bio_tag = "I-"
225
+ final_label_to_submit = bio_tag + current_label_pred
226
+ previous_pii_type_simplified = current_label_pred
227
+ else:
228
+ if current_label_pred != "O":
229
+ final_label_to_submit = current_label_pred
230
+ else:
231
+ continue
232
+
233
+ if final_label_to_submit:
234
+ final_predictions_for_submission.append({
235
+ "document": doc_id,
236
+ "token": current_token_id,
237
+ "label": final_label_to_submit,
238
+ "token_str": row["token_str"]
239
+ })
240
+
241
+ previous_token_id = current_token_id
242
+ else:
243
+ print("No PII predictions made after initial filtering.")
244
+
245
+
246
+ # %%
247
+ df_final_submission = pd.DataFrame(final_predictions_for_submission)
248
+ if not df_final_submission.empty:
249
+ df_final_submission["row_id"] = list(range(len(df_final_submission)))
250
+ print(df_final_submission.head(20))
251
+ df_submission = df_final_submission[["row_id", "document", "token", "label"]]
252
+ else:
253
+ df_submission = pd.DataFrame(columns=["row_id", "document", "token", "label"])
254
+ print("Final DataFrame is empty. No PII entities to submit.")
255
+
256
+
257
+ # %%
258
+ df_submission.to_csv("submission.csv", index=False)
259
+ print("Submission file created.")
260
+
261
+ # %%
262
+
263
+
264
+
code/inference_actual_orig.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # ## Introduction
3
+ #
4
+ # Congratulations to everyone who persevered through this challenging competition!
5
+ #
6
+ # I've been eagerly looking forward to reading the top solutions, and now that the competition has finally concluded, I am thrilled.
7
+ #
8
+ # It has been a great learning journey for all of us. I am excited to share that I plan to publish a training notebook that demonstrates how to achieve a score of `Private Leaderboard 0.963` using a single model.
9
+ #
10
+ # [Training Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/training-single-model-to-get-privatelb-0-963/)
11
+ #
12
+ # And this notebook shows how to submit the model trained above.
13
+ #
14
+ #
15
+ # ## Acknowledge
16
+ #
17
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
18
+ #
19
+ # For training a single model the above notebook shows, it takes less than 40min!
20
+
21
+ # %%
22
+ import re
23
+ import os
24
+ import json
25
+ import torch
26
+ import argparse
27
+ from pathlib import Path
28
+ from itertools import chain
29
+
30
+ import numpy as np
31
+ import pandas as pd
32
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification
33
+ from datasets import Dataset
34
+ from spacy.lang.en import English
35
+
36
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
37
+
38
+ # %%
39
+ !ls ../input/
40
+
41
+ # %%
42
+ backbone = 'microsoft/deberta-v3-large'
43
+ model_dir = '/kaggle/input/pii-dbtv3-large-9688'
44
+ th = 0.5
45
+
46
+ id2label = {0: 'B-EMAIL', 1: 'B-ID_NUM', 2: 'B-NAME_STUDENT', 3: 'B-PHONE_NUM', 4: 'B-STREET_ADDRESS', 5: 'B-URL_PERSONAL', 6: 'B-USERNAME', 7: 'I-ID_NUM', 8: 'I-NAME_STUDENT', 9: 'I-PHONE_NUM', 10: 'I-STREET_ADDRESS', 11: 'I-URL_PERSONAL', 12: 'O'}
47
+
48
+ # %%
49
+ def tokenize(example, tokenizer):
50
+ text = []
51
+ token_map = []
52
+
53
+ idx = 0
54
+
55
+ for t, ws in zip(example["tokens"], example["trailing_whitespace"]):
56
+
57
+ text.append(t)
58
+ token_map.extend([idx]*len(t))
59
+ if ws:
60
+ text.append(" ")
61
+ token_map.append(-1)
62
+
63
+ idx += 1
64
+
65
+
66
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
67
+
68
+
69
+ return {
70
+ **tokenized,
71
+ "token_map": token_map,
72
+ }
73
+
74
+ # %%
75
+ with open("/kaggle/input/pii-detection-removal-from-educational-data/test.json", "r") as f:
76
+ data = json.load(f)
77
+
78
+ ds = Dataset.from_dict({
79
+ "full_text": [x["full_text"] for x in data],
80
+ "document": [x["document"] for x in data],
81
+ "tokens": [x["tokens"] for x in data],
82
+ "trailing_whitespace": [x["trailing_whitespace"] for x in data],
83
+ })
84
+
85
+ tokenizer = AutoTokenizer.from_pretrained(os.path.join(model_dir, 'fold0'))
86
+ collator = DataCollatorForTokenClassification(tokenizer)
87
+ ds = ds.map(tokenize, fn_kwargs={"tokenizer": tokenizer}, num_proc=2)
88
+
89
+ # %%
90
+ ds[0].keys()
91
+
92
+ # %%
93
+ def get_model(conf):
94
+ return AutoModelForTokenClassification.from_config(
95
+ conf
96
+ )
97
+
98
+ fold = 0
99
+ conf = AutoConfig.from_pretrained(os.path.join(model_dir, f'fold{fold}'))
100
+ model = get_model(conf)
101
+ # model_file = os.path.join(model_dir, f'fold{fold}', f'genv1_noext1_ds3_ht1024_dbtv3_large_wu0.1_w10_ado4_wd0_bs2_lr1e5_5f_5epo_fold{fold}_best.pth')
102
+ model_file = "/kaggle/input/single_model_only_comp_data/pytorch/training_length_4096/1/deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth"
103
+ sd = torch.load(model_file, map_location='cpu')
104
+ model.load_state_dict(sd, strict=True)
105
+ model.eval()
106
+ model.cuda()
107
+
108
+
109
+ # %%
110
+ !nvidia-smi
111
+
112
+ # %%
113
+ preds_final = []
114
+ with torch.no_grad():
115
+ for sample in ds:
116
+ input_ids = torch.tensor([sample['input_ids']]).to(device)
117
+ attention_mask = torch.tensor([sample['attention_mask']]).to(device)
118
+
119
+ preds = model(input_ids, attention_mask=attention_mask, labels=None)['logits'].softmax(2)
120
+ preds[:, :, -1][preds[:, :, -1] < th] = 0
121
+ preds = torch.argmax(preds, axis=2).cpu().numpy()
122
+ preds_final.append(preds[0])
123
+
124
+
125
+ # %%
126
+ !nvidia-smi
127
+
128
+ # %%
129
+ pairs = []
130
+ processed = []
131
+ for p, token_map, offsets, tokens, doc in zip(preds_final, ds["token_map"],
132
+ ds["offset_mapping"], ds["tokens"],
133
+ ds["document"]):
134
+ for token_pred, (start_idx, end_idx) in zip(p, offsets):
135
+ label_pred = id2label[token_pred]
136
+
137
+ if start_idx + end_idx == 0:
138
+ continue
139
+
140
+ if token_map[start_idx] == -1:
141
+ start_idx += 1
142
+
143
+ # ignore "\n\n"
144
+ while start_idx < len(token_map) and tokens[token_map[start_idx]].isspace():
145
+ start_idx += 1
146
+
147
+ if start_idx >= len(token_map):
148
+ break
149
+
150
+ token_id = token_map[start_idx]
151
+
152
+ # ignore "O" predictions and whitespace preds
153
+ if label_pred not in ("O") and token_id != -1:
154
+ # if label_pred not in ("O", "B-EMAIL", "B-PHONE_NUM", "I-PHONE_NUM") and token_id != -1:
155
+ pair = (doc, token_id)
156
+
157
+ if pair not in pairs:
158
+ processed.append({"document": doc, "token": token_id, "label": label_pred, "token_str": tokens[token_id]})
159
+ pairs.append(pair)
160
+
161
+ # %%
162
+ df = pd.DataFrame(processed)
163
+ df["row_id"] = list(range(len(df)))
164
+ display(df.head(100))
165
+
166
+ # %%
167
+ df[["row_id", "document", "token", "label"]].to_csv("submission.csv", index=False)
168
+
169
+ # %%
170
+
171
+
172
+
code/kaggle_upload.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import kagglehub
2
+
3
+ kagglehub.login()
4
+
5
+ # Replace with path to directory containing model files.
6
+ LOCAL_MODEL_DIR = '/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo_simplified_labels_fold0_best_hf'
7
+
8
+ MODEL_SLUG = 'Single_model_only_comp_data' # Replace with model slug.
9
+
10
+ # Learn more about naming model variations at
11
+ # https://www.kaggle.com/docs/models#name-model.
12
+ VARIATION_SLUG = 'training_length_4096_8labels_moredata' # Replace with variation slug.
13
+
14
+ kagglehub.model_upload(
15
+ handle = f"andreifrcanu/{MODEL_SLUG}/transformers/{VARIATION_SLUG}",
16
+ local_model_dir = LOCAL_MODEL_DIR,
17
+ version_notes = 'training_length_4096_8labels_moredata')
18
+ # kagglehub.model_upload(
19
+ # handle = f"andreifrcanu/{MODEL_SLUG}/pyTorch/{VARIATION_SLUG}",
20
+ # local_model_dir = LOCAL_MODEL_DIR,
21
+ # version_notes = 'training_length_4096_origlabels_actualmoredata')
code/submission.csv ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ row_id,document,token,label
2
+ 0,7,9,B-NAME_STUDENT
3
+ 1,7,10,I-NAME_STUDENT
4
+ 2,7,52,B-NAME_STUDENT
5
+ 3,7,482,B-NAME_STUDENT
6
+ 4,7,483,I-NAME_STUDENT
7
+ 5,7,741,B-NAME_STUDENT
8
+ 6,7,742,I-NAME_STUDENT
9
+ 7,10,0,B-NAME_STUDENT
10
+ 8,10,1,I-NAME_STUDENT
11
+ 9,10,465,B-NAME_STUDENT
12
+ 10,16,4,B-NAME_STUDENT
13
+ 11,16,5,I-NAME_STUDENT
14
+ 12,20,5,B-NAME_STUDENT
15
+ 13,20,6,I-NAME_STUDENT
16
+ 14,56,13,B-NAME_STUDENT
17
+ 15,86,6,B-NAME_STUDENT
18
+ 16,86,7,I-NAME_STUDENT
19
+ 17,93,0,B-NAME_STUDENT
20
+ 18,93,1,I-NAME_STUDENT
21
+ 19,104,8,B-NAME_STUDENT
22
+ 20,104,9,I-NAME_STUDENT
23
+ 21,112,6,B-NAME_STUDENT
24
+ 22,123,33,B-NAME_STUDENT
25
+ 23,123,35,B-ID_NUM
26
+ 24,123,38,B-NAME_STUDENT
27
+ 25,123,224,B-NAME_STUDENT
28
+ 26,123,741,B-URL_PERSONAL
29
+ 27,123,1509,B-URL_PERSONAL
30
+ 28,123,1549,B-URL_PERSONAL
31
+ 29,123,1550,I-URL_PERSONAL
32
+ 30,123,1575,B-URL_PERSONAL
33
+ 31,123,1591,B-URL_PERSONAL
34
+ 32,123,1648,B-URL_PERSONAL
35
+ 33,123,1690,B-URL_PERSONAL
36
+ 34,123,1691,I-URL_PERSONAL
code/training-actualorig-withotherdata.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
3
+ #
4
+ # # Introduction
5
+ #
6
+ # Hi all,
7
+ #
8
+ # In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
9
+ #
10
+ # Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
11
+ #
12
+ # Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
13
+ #
14
+ # Let's dive into the details and uncover the techniques that led to a successful submission!
15
+ #
16
+ # [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
17
+ #
18
+ # # Summary
19
+ #
20
+ # * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
21
+ # * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
22
+ # * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
23
+ # * For inference, no truncation.
24
+ # * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
25
+ # * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
26
+ # * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
27
+ # * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
28
+ # * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
29
+ # * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
30
+ # * cosine with warmup learning rate schedule -- almost became a standard setting.
31
+ # * warmup 0.5 epo
32
+ # * cosine 4.5 epo
33
+ #
34
+ # # Acknowledge
35
+ #
36
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
37
+ #
38
+ # For training a single model this notebook shows, it takes less than 40min!
39
+ #
40
+
41
+ # %%
42
+ import os
43
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
44
+ import warnings
45
+ warnings.filterwarnings("ignore")
46
+ DEBUG=False
47
+
48
+ # %%
49
+ # !pip install -q seqeval
50
+
51
+ # %%
52
+ import gc
53
+ import json
54
+ import copy
55
+ import time
56
+ import pickle
57
+ import numpy as np
58
+ import pandas as pd
59
+ from itertools import chain
60
+ from transformers import AutoTokenizer
61
+ from transformers import AutoModelForTokenClassification
62
+ from transformers import get_cosine_schedule_with_warmup
63
+ from tqdm import tqdm
64
+
65
+ from seqeval.metrics import recall_score, precision_score
66
+ from seqeval.metrics import f1_score
67
+
68
+ import torch
69
+ from torch import nn
70
+ from torch.utils.data import Dataset, DataLoader
71
+ from torch.nn.utils.rnn import pad_sequence
72
+ from torch.nn import CrossEntropyLoss
73
+ from torch.optim import AdamW
74
+
75
+ import matplotlib.pyplot as plt
76
+ from pylab import rcParams
77
+
78
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
79
+
80
+ # %% [markdown]
81
+ # # Config
82
+
83
+ # %%
84
+ exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo'
85
+
86
+ backbone = 'microsoft/deberta-v3-large'
87
+
88
+ TRAINING_MAX_LENGTH = 4096
89
+ batch_size = 2
90
+ lr = 1e-5
91
+ ds_rate = 0.3
92
+ attention_probs_dropout_prob = 0.4
93
+ hidden_dropout_prob = 0.1
94
+ wd = 0.
95
+ pos_weight = 10
96
+ n_epochs = 5
97
+
98
+ use_amp = True
99
+
100
+ # %% [markdown]
101
+ # # Data
102
+
103
+ # %%
104
+ from sklearn.model_selection import train_test_split
105
+
106
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
107
+ for sample in data:
108
+ if len(set(sample['labels'])) > 1:
109
+ sample['is_pos'] = 1
110
+ else:
111
+ sample['is_pos'] = 0
112
+ print("original datapoints: ", len(data))
113
+
114
+
115
+ moredata = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/moredata_dataset_fixed.json'))
116
+ print("moredata datapoints: ", len(moredata))
117
+
118
+
119
+ # %%
120
+ from sklearn.model_selection import GroupKFold
121
+ import random
122
+ random.seed(0)
123
+ kf = GroupKFold(n_splits=5)
124
+ folds = []
125
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
126
+ folds.append([data[i]['document'] for i in valid_index])
127
+
128
+ # %%
129
+ all_labels = sorted(list(set(chain(*[x["labels"] for x in data]))))
130
+ label2id = {l: i for i,l in enumerate(all_labels)}
131
+ id2label = {v:k for k,v in label2id.items()}
132
+
133
+ target = [
134
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
135
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
136
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
137
+ ]
138
+
139
+ print(len(all_labels), id2label)
140
+
141
+ # %% [markdown]
142
+ # # Dataset
143
+
144
+ # %%
145
+ def tokenize(example, tokenizer, label2id, max_length):
146
+
147
+ # rebuild text from tokens
148
+ text = []
149
+ labels = []
150
+
151
+ for t, l, ws in zip(
152
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
153
+ ):
154
+ text.append(t)
155
+ labels.extend([l] * len(t))
156
+
157
+ if ws:
158
+ text.append(" ")
159
+ labels.append("O")
160
+
161
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
162
+ if len(tokenized['input_ids']) > max_length:
163
+ for key in tokenized.keys():
164
+ tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
165
+
166
+ labels = np.array(labels)
167
+
168
+ text = "".join(text)
169
+ token_labels = []
170
+
171
+ for start_idx, end_idx in tokenized.offset_mapping:
172
+ # CLS token
173
+ if start_idx == 0 and end_idx == 0:
174
+ token_labels.append(label2id["O"])
175
+ continue
176
+
177
+ # case when token starts with whitespace
178
+ if text[start_idx].isspace():
179
+ start_idx += 1
180
+
181
+ token_labels.append(label2id[labels[start_idx]])
182
+
183
+ length = len(tokenized.input_ids)
184
+
185
+ return {**tokenized, "labels": token_labels, "length": length}
186
+
187
+ # %%
188
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
189
+
190
+ class PIIDataset(Dataset):
191
+ def __init__(self, tokenizer, data, label2id, max_length):
192
+ self.tokenizer = tokenizer
193
+ self.data = copy.deepcopy(data)
194
+ self.label2id = label2id
195
+ self.max_length = max_length
196
+
197
+ for i in tqdm(range(len(self.data))):
198
+ if self.data[i].get('input_ids') is None:
199
+ self.data[i]['provided_labels'] = self.data[i]['labels']
200
+ self.data[i] = tokenize(
201
+ self.data[i],
202
+ self.tokenizer,
203
+ self.label2id,
204
+ self.max_length
205
+ )
206
+
207
+ def __len__(self):
208
+ return len(self.data)
209
+
210
+ def __getitem__(self, idx):
211
+ return self.data[idx]
212
+
213
+
214
+ def collate_fn(batch):
215
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
216
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
217
+ labels = [torch.tensor(x['labels']) for x in batch]
218
+
219
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=0)
220
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
221
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
222
+
223
+ return {
224
+ 'input_ids': input_ids_padded,
225
+ 'attention_mask': attention_masks_padded,
226
+ 'labels': labels_padded,
227
+ }
228
+
229
+ ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH)
230
+ len(ds[9]['input_ids'])
231
+
232
+ # %% [markdown]
233
+ # # Model
234
+
235
+ # %%
236
+ model = AutoModelForTokenClassification.from_pretrained(
237
+ backbone,
238
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
239
+ hidden_dropout_prob=hidden_dropout_prob,
240
+ num_labels=len(all_labels),
241
+ id2label=id2label,
242
+ label2id=label2id,
243
+ ignore_mismatched_sizes=True
244
+ )
245
+
246
+ # %% [markdown]
247
+ # # Loss
248
+
249
+ # %%
250
+ weights = torch.ones(13) * pos_weight
251
+ weights[12] = 1
252
+
253
+ CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
254
+ def criterion(logits, labels):
255
+ return CE(logits.view(-1, len(all_labels)), labels.view(-1))
256
+
257
+ # %% [markdown]
258
+ # # Metric
259
+
260
+ # %%
261
+ def compute_metrics(p, all_labels, th=0.5):
262
+ predictions, labels = p
263
+ true_predictions = []
264
+ true_labels = []
265
+ for tmp, batch_l in zip(predictions, labels):
266
+ batch_p = copy.deepcopy(tmp)
267
+ batch_p[:, :, -1][batch_p[:, :, -1] < th] = -9999
268
+ batch_p = np.argmax(batch_p, axis=2)
269
+ true_predictions.extend([all_labels[p] for (p, l) in zip(batch_p.reshape(-1), batch_l.reshape(-1)) if l != -100])
270
+ true_labels.extend([all_labels[l] for l in batch_l.reshape(-1) if l != -100])
271
+
272
+ recall = recall_score([true_labels], [true_predictions])
273
+ precision = precision_score([true_labels], [true_predictions])
274
+ f1_score = (1 + 5*5) * recall * precision / (5*5*precision + recall)
275
+
276
+ results = {
277
+ 'recall': recall,
278
+ 'precision': precision,
279
+ 'f1': f1_score
280
+ }
281
+ return results
282
+
283
+
284
+ # %% [markdown]
285
+ # # Train & Valid Func
286
+
287
+ # %%
288
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
289
+ model.train()
290
+ running_loss = []
291
+ bar = tqdm(loader, total=len(loader))
292
+ for batch in bar:
293
+ optimizer.zero_grad()
294
+ scheduler.step()
295
+
296
+ input_ids = batch['input_ids'].to(device)
297
+ attention_mask = batch['attention_mask'].to(device)
298
+ labels = batch['labels'].to(device)
299
+
300
+ if scaler is not None:
301
+ with torch.cuda.amp.autocast():
302
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
303
+ loss = criterion(outputs['logits'], labels)
304
+ scaler.scale(loss).backward()
305
+ scaler.step(optimizer)
306
+ scaler.update()
307
+ else:
308
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
309
+ loss = criterion(outputs['logits'], labels)
310
+ loss.backward()
311
+ optimizer.step()
312
+
313
+ running_loss.append(loss.item())
314
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
315
+
316
+ return np.mean(running_loss)
317
+
318
+
319
+ def valid_one_epoch(model, loader):
320
+ model.eval()
321
+ running_loss = []
322
+ bar = tqdm(loader, total=len(loader))
323
+ predictions = []
324
+ labels = []
325
+
326
+ with torch.no_grad():
327
+ for batch in bar:
328
+ input_ids = batch['input_ids'].to(device)
329
+ attention_mask = batch['attention_mask'].to(device)
330
+ labels_ = batch['labels'].to(device)
331
+
332
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
333
+ loss = criterion(outputs['logits'], labels_)
334
+
335
+ running_loss.append(loss.item())
336
+ predictions.append(outputs['logits'].detach().cpu().numpy())
337
+ labels.append(labels_.detach().cpu().numpy())
338
+
339
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
340
+
341
+ return np.mean(running_loss), compute_metrics((predictions, labels), all_labels)
342
+
343
+ # %% [markdown]
344
+ # # Scheduler
345
+
346
+ # %%
347
+ rcParams['figure.figsize'] = 20, 2
348
+
349
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
350
+ return get_cosine_schedule_with_warmup(
351
+ optimizer,
352
+ int(num_training_steps * warmup_steps_ratio),
353
+ num_training_steps
354
+ )
355
+
356
+ opt = AdamW(model.parameters(), lr=lr)
357
+ sch = get_scheduler(opt, 100, 0.1)
358
+
359
+ lrs = []
360
+ for epoch in range(1, 100+1):
361
+ sch.step()
362
+ lrs.append(opt.param_groups[0]["lr"])
363
+
364
+ plt.plot(range(len(lrs)), lrs)
365
+
366
+ # %% [markdown]
367
+ # # Run
368
+
369
+ # %%
370
+ def run(fold,model):
371
+
372
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
373
+ model_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best.pth')
374
+
375
+ valid_doc_ids = folds[fold]
376
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
377
+
378
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
379
+
380
+ dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH)
381
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH)
382
+ print(len(dataset_train), len(dataset_valid))
383
+
384
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=8, collate_fn=collate_fn)
385
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=8, collate_fn=collate_fn)
386
+
387
+
388
+ # model = get_model()
389
+ model = model.to(device)
390
+
391
+ optimizer = AdamW(model.parameters(), lr=lr, weight_decay=wd)
392
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
393
+ from_epoch = 0
394
+ metric_best = 0
395
+
396
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
397
+
398
+ for epoch in range(1, n_epochs+1):
399
+ if epoch < from_epoch + 1:
400
+ print(logs[epoch-1])
401
+ continue
402
+
403
+ print(time.ctime(), 'Epoch:', epoch)
404
+
405
+ train_loss = train_one_epoch(model, loader_train, optimizer, scheduler, scaler)
406
+ valid_loss, result = valid_one_epoch(model, loader_valid)
407
+ metric = result["f1"]
408
+
409
+ content = time.ctime() + ' ' + \
410
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
411
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
412
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric: {metric:.4f}.'
413
+ print(content)
414
+ with open(log_file, 'a') as appender:
415
+ appender.write(content + '\n')
416
+
417
+ if metric > metric_best:
418
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model ...')
419
+ # if not DEBUG:
420
+ torch.save(model.state_dict(), model_file)
421
+ metric_best = metric
422
+
423
+ # Save Last
424
+ if not DEBUG:
425
+ torch.save(
426
+ {
427
+ 'epoch': epoch,
428
+ 'model_state_dict': model.state_dict(),
429
+ 'optimizer_state_dict': optimizer.state_dict(),
430
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
431
+ 'metric_best': metric_best,
432
+ },
433
+ model_file.replace('_best', '_last')
434
+ )
435
+
436
+ del model
437
+ torch.cuda.empty_cache()
438
+ gc.collect()
439
+
440
+ # %%
441
+ run(0,model)
442
+
443
+ # %%
444
+
445
+
446
+
code/training-single-model-to-get-privatelb-0-963.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
code/training-single-model-to-get-privatelb-0-963_actualorig.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
3
+ #
4
+ # # Introduction
5
+ #
6
+ # Hi all,
7
+ #
8
+ # In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
9
+ #
10
+ # Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
11
+ #
12
+ # Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
13
+ #
14
+ # Let's dive into the details and uncover the techniques that led to a successful submission!
15
+ #
16
+ # [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
17
+ #
18
+ # # Summary
19
+ #
20
+ # * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
21
+ # * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
22
+ # * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
23
+ # * For inference, no truncation.
24
+ # * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
25
+ # * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
26
+ # * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
27
+ # * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
28
+ # * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
29
+ # * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
30
+ # * cosine with warmup learning rate schedule -- almost became a standard setting.
31
+ # * warmup 0.5 epo
32
+ # * cosine 4.5 epo
33
+ #
34
+ # # Acknowledge
35
+ #
36
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
37
+ #
38
+ # For training a single model this notebook shows, it takes less than 40min!
39
+ #
40
+
41
+ # %%
42
+ import os
43
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
44
+ import warnings
45
+ warnings.filterwarnings("ignore")
46
+ DEBUG=False
47
+
48
+ # %%
49
+ # !pip install -q seqeval
50
+
51
+ # %%
52
+ import gc
53
+ import json
54
+ import copy
55
+ import time
56
+ import pickle
57
+ import numpy as np
58
+ import pandas as pd
59
+ from itertools import chain
60
+ from transformers import AutoTokenizer
61
+ from transformers import AutoModelForTokenClassification
62
+ from transformers import get_cosine_schedule_with_warmup
63
+ from tqdm import tqdm
64
+
65
+ from seqeval.metrics import recall_score, precision_score
66
+ from seqeval.metrics import f1_score
67
+
68
+ import torch
69
+ from torch import nn
70
+ from torch.utils.data import Dataset, DataLoader
71
+ from torch.nn.utils.rnn import pad_sequence
72
+ from torch.nn import CrossEntropyLoss
73
+ from torch.optim import AdamW
74
+
75
+ import matplotlib.pyplot as plt
76
+ from pylab import rcParams
77
+
78
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
79
+
80
+ # %% [markdown]
81
+ # # Config
82
+
83
+ # %%
84
+ exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo'
85
+
86
+ backbone = 'microsoft/deberta-v3-large'
87
+
88
+ TRAINING_MAX_LENGTH = 4096
89
+ batch_size = 2
90
+ lr = 1e-5
91
+ ds_rate = 0.3
92
+ attention_probs_dropout_prob = 0.4
93
+ hidden_dropout_prob = 0.1
94
+ wd = 0.
95
+ pos_weight = 10
96
+ n_epochs = 5
97
+
98
+ use_amp = True
99
+
100
+ # %% [markdown]
101
+ # # Data
102
+
103
+ # %%
104
+ from sklearn.model_selection import train_test_split
105
+
106
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
107
+ for sample in data:
108
+ if len(set(sample['labels'])) > 1:
109
+ sample['is_pos'] = 1
110
+ else:
111
+ sample['is_pos'] = 0
112
+ print("original datapoints: ", len(data))
113
+
114
+
115
+ # moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
116
+ # print("moredata datapoints: ", len(moredata))
117
+
118
+
119
+ # %%
120
+ from sklearn.model_selection import GroupKFold
121
+ import random
122
+ random.seed(0)
123
+ kf = GroupKFold(n_splits=5)
124
+ folds = []
125
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
126
+ folds.append([data[i]['document'] for i in valid_index])
127
+
128
+ # %%
129
+ all_labels = sorted(list(set(chain(*[x["labels"] for x in data]))))
130
+ label2id = {l: i for i,l in enumerate(all_labels)}
131
+ id2label = {v:k for k,v in label2id.items()}
132
+
133
+ target = [
134
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
135
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
136
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
137
+ ]
138
+
139
+ print(len(all_labels), id2label)
140
+
141
+ # %% [markdown]
142
+ # # Dataset
143
+
144
+ # %%
145
+ def tokenize(example, tokenizer, label2id, max_length):
146
+
147
+ # rebuild text from tokens
148
+ text = []
149
+ labels = []
150
+
151
+ for t, l, ws in zip(
152
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
153
+ ):
154
+ text.append(t)
155
+ labels.extend([l] * len(t))
156
+
157
+ if ws:
158
+ text.append(" ")
159
+ labels.append("O")
160
+
161
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
162
+ if len(tokenized['input_ids']) > max_length:
163
+ for key in tokenized.keys():
164
+ tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
165
+
166
+ labels = np.array(labels)
167
+
168
+ text = "".join(text)
169
+ token_labels = []
170
+
171
+ for start_idx, end_idx in tokenized.offset_mapping:
172
+ # CLS token
173
+ if start_idx == 0 and end_idx == 0:
174
+ token_labels.append(label2id["O"])
175
+ continue
176
+
177
+ # case when token starts with whitespace
178
+ if text[start_idx].isspace():
179
+ start_idx += 1
180
+
181
+ token_labels.append(label2id[labels[start_idx]])
182
+
183
+ length = len(tokenized.input_ids)
184
+
185
+ return {**tokenized, "labels": token_labels, "length": length}
186
+
187
+ # %%
188
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
189
+
190
+ class PIIDataset(Dataset):
191
+ def __init__(self, tokenizer, data, label2id, max_length):
192
+ self.tokenizer = tokenizer
193
+ self.data = copy.deepcopy(data)
194
+ self.label2id = label2id
195
+ self.max_length = max_length
196
+
197
+ for i in tqdm(range(len(self.data))):
198
+ if self.data[i].get('input_ids') is None:
199
+ self.data[i]['provided_labels'] = self.data[i]['labels']
200
+ self.data[i] = tokenize(
201
+ self.data[i],
202
+ self.tokenizer,
203
+ self.label2id,
204
+ self.max_length
205
+ )
206
+
207
+ def __len__(self):
208
+ return len(self.data)
209
+
210
+ def __getitem__(self, idx):
211
+ return self.data[idx]
212
+
213
+
214
+ def collate_fn(batch):
215
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
216
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
217
+ labels = [torch.tensor(x['labels']) for x in batch]
218
+
219
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=0)
220
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
221
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
222
+
223
+ return {
224
+ 'input_ids': input_ids_padded,
225
+ 'attention_mask': attention_masks_padded,
226
+ 'labels': labels_padded,
227
+ }
228
+
229
+ ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH)
230
+ len(ds[9]['input_ids'])
231
+
232
+ # %% [markdown]
233
+ # # Model
234
+
235
+ # %%
236
+ model = AutoModelForTokenClassification.from_pretrained(
237
+ backbone,
238
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
239
+ hidden_dropout_prob=hidden_dropout_prob,
240
+ num_labels=len(all_labels),
241
+ id2label=id2label,
242
+ label2id=label2id,
243
+ ignore_mismatched_sizes=True
244
+ )
245
+
246
+ # %% [markdown]
247
+ # # Loss
248
+
249
+ # %%
250
+ weights = torch.ones(13) * pos_weight
251
+ weights[12] = 1
252
+
253
+ CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
254
+ def criterion(logits, labels):
255
+ return CE(logits.view(-1, len(all_labels)), labels.view(-1))
256
+
257
+ # %% [markdown]
258
+ # # Metric
259
+
260
+ # %%
261
+ def compute_metrics(p, all_labels, th=0.5):
262
+ predictions, labels = p
263
+ true_predictions = []
264
+ true_labels = []
265
+ for tmp, batch_l in zip(predictions, labels):
266
+ batch_p = copy.deepcopy(tmp)
267
+ batch_p[:, :, -1][batch_p[:, :, -1] < th] = -9999
268
+ batch_p = np.argmax(batch_p, axis=2)
269
+ true_predictions.extend([all_labels[p] for (p, l) in zip(batch_p.reshape(-1), batch_l.reshape(-1)) if l != -100])
270
+ true_labels.extend([all_labels[l] for l in batch_l.reshape(-1) if l != -100])
271
+
272
+ recall = recall_score([true_labels], [true_predictions])
273
+ precision = precision_score([true_labels], [true_predictions])
274
+ f1_score = (1 + 5*5) * recall * precision / (5*5*precision + recall)
275
+
276
+ results = {
277
+ 'recall': recall,
278
+ 'precision': precision,
279
+ 'f1': f1_score
280
+ }
281
+ return results
282
+
283
+
284
+ # %% [markdown]
285
+ # # Train & Valid Func
286
+
287
+ # %%
288
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
289
+ model.train()
290
+ running_loss = []
291
+ bar = tqdm(loader, total=len(loader))
292
+ for batch in bar:
293
+ optimizer.zero_grad()
294
+ scheduler.step()
295
+
296
+ input_ids = batch['input_ids'].to(device)
297
+ attention_mask = batch['attention_mask'].to(device)
298
+ labels = batch['labels'].to(device)
299
+
300
+ if scaler is not None:
301
+ with torch.cuda.amp.autocast():
302
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
303
+ loss = criterion(outputs['logits'], labels)
304
+ scaler.scale(loss).backward()
305
+ scaler.step(optimizer)
306
+ scaler.update()
307
+ else:
308
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
309
+ loss = criterion(outputs['logits'], labels)
310
+ loss.backward()
311
+ optimizer.step()
312
+
313
+ running_loss.append(loss.item())
314
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
315
+
316
+ return np.mean(running_loss)
317
+
318
+
319
+ def valid_one_epoch(model, loader):
320
+ model.eval()
321
+ running_loss = []
322
+ bar = tqdm(loader, total=len(loader))
323
+ predictions = []
324
+ labels = []
325
+
326
+ with torch.no_grad():
327
+ for batch in bar:
328
+ input_ids = batch['input_ids'].to(device)
329
+ attention_mask = batch['attention_mask'].to(device)
330
+ labels_ = batch['labels'].to(device)
331
+
332
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
333
+ loss = criterion(outputs['logits'], labels_)
334
+
335
+ running_loss.append(loss.item())
336
+ predictions.append(outputs['logits'].detach().cpu().numpy())
337
+ labels.append(labels_.detach().cpu().numpy())
338
+
339
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
340
+
341
+ return np.mean(running_loss), compute_metrics((predictions, labels), all_labels)
342
+
343
+ # %% [markdown]
344
+ # # Scheduler
345
+
346
+ # %%
347
+ rcParams['figure.figsize'] = 20, 2
348
+
349
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
350
+ return get_cosine_schedule_with_warmup(
351
+ optimizer,
352
+ int(num_training_steps * warmup_steps_ratio),
353
+ num_training_steps
354
+ )
355
+
356
+ opt = AdamW(model.parameters(), lr=lr)
357
+ sch = get_scheduler(opt, 100, 0.1)
358
+
359
+ lrs = []
360
+ for epoch in range(1, 100+1):
361
+ sch.step()
362
+ lrs.append(opt.param_groups[0]["lr"])
363
+
364
+ plt.plot(range(len(lrs)), lrs)
365
+
366
+ # %% [markdown]
367
+ # # Run
368
+
369
+ # %%
370
+ def run(fold,model):
371
+
372
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
373
+ model_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best.pth')
374
+
375
+ valid_doc_ids = folds[fold]
376
+ # train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
377
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
378
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
379
+
380
+ dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH)
381
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH)
382
+ print(len(dataset_train), len(dataset_valid))
383
+
384
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=8, collate_fn=collate_fn)
385
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=8, collate_fn=collate_fn)
386
+
387
+
388
+ # model = get_model()
389
+ model = model.to(device)
390
+
391
+ optimizer = AdamW(model.parameters(), lr=lr, weight_decay=wd)
392
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
393
+ from_epoch = 0
394
+ metric_best = 0
395
+
396
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
397
+
398
+ for epoch in range(1, n_epochs+1):
399
+ if epoch < from_epoch + 1:
400
+ print(logs[epoch-1])
401
+ continue
402
+
403
+ print(time.ctime(), 'Epoch:', epoch)
404
+
405
+ train_loss = train_one_epoch(model, loader_train, optimizer, scheduler, scaler)
406
+ valid_loss, result = valid_one_epoch(model, loader_valid)
407
+ metric = result["f1"]
408
+
409
+ content = time.ctime() + ' ' + \
410
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
411
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
412
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric: {metric:.4f}.'
413
+ print(content)
414
+ with open(log_file, 'a') as appender:
415
+ appender.write(content + '\n')
416
+
417
+ if metric > metric_best:
418
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model ...')
419
+ # if not DEBUG:
420
+ torch.save(model.state_dict(), model_file)
421
+ metric_best = metric
422
+
423
+ # Save Last
424
+ if not DEBUG:
425
+ torch.save(
426
+ {
427
+ 'epoch': epoch,
428
+ 'model_state_dict': model.state_dict(),
429
+ 'optimizer_state_dict': optimizer.state_dict(),
430
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
431
+ 'metric_best': metric_best,
432
+ },
433
+ model_file.replace('_best', '_last')
434
+ )
435
+
436
+ del model
437
+ torch.cuda.empty_cache()
438
+ gc.collect()
439
+
440
+ # %%
441
+ run(0,model)
442
+
443
+ # %%
444
+
445
+
446
+
code/training-single-model-to-get-privatelb-0-963_orig.py ADDED
@@ -0,0 +1,594 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
3
+ #
4
+ # # Introduction
5
+ #
6
+ # Hi all,
7
+ #
8
+ # In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
9
+ #
10
+ # Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
11
+ #
12
+ # Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
13
+ #
14
+ # Let's dive into the details and uncover the techniques that led to a successful submission!
15
+ #
16
+ # [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
17
+ #
18
+ # # Summary
19
+ #
20
+ # * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
21
+ # * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
22
+ # * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
23
+ # * For inference, no truncation.
24
+ # * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
25
+ # * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
26
+ # * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
27
+ # * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
28
+ # * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
29
+ # * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
30
+ # * cosine with warmup learning rate schedule -- almost became a standard setting.
31
+ # * warmup 0.5 epo
32
+ # * cosine 4.5 epo
33
+ #
34
+ # # Acknowledge
35
+ #
36
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
37
+ #
38
+ # For training a single model this notebook shows, it takes less than 40min!
39
+ #
40
+
41
+ # %%
42
+ import os
43
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
44
+ import warnings
45
+ warnings.filterwarnings("ignore")
46
+ DEBUG=False
47
+
48
+ USE_SIMPLIFIED_LABELS = False # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS
49
+
50
+ # %%
51
+ # !pip install -q seqeval
52
+
53
+ # %%
54
+ import gc
55
+ import json
56
+ import copy
57
+ import time
58
+ import pickle
59
+ import numpy as np
60
+ import pandas as pd
61
+ from itertools import chain
62
+ from transformers import AutoTokenizer
63
+ from transformers import AutoModelForTokenClassification
64
+ from transformers import get_cosine_schedule_with_warmup
65
+ from tqdm import tqdm
66
+
67
+ from seqeval.metrics import recall_score, precision_score
68
+ from seqeval.metrics import f1_score
69
+
70
+ import torch
71
+ from torch import nn
72
+ from torch.utils.data import Dataset, DataLoader
73
+ from torch.nn.utils.rnn import pad_sequence
74
+ from torch.nn import CrossEntropyLoss
75
+ from torch.optim import AdamW
76
+
77
+ import matplotlib.pyplot as plt
78
+ from pylab import rcParams
79
+
80
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
81
+
82
+ # %% [markdown]
83
+ # # Config
84
+
85
+ # %%
86
+ exp_name_base = 'deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo' # Changed ado4 to ado1
87
+ exp_name = f"{exp_name_base}_{'simplified_labels' if USE_SIMPLIFIED_LABELS else 'original_bio_labels'}"
88
+
89
+
90
+ backbone = 'microsoft/deberta-v3-large'
91
+
92
+ TRAINING_MAX_LENGTH = 4096
93
+ batch_size = 2
94
+ lr = 1e-5
95
+ ds_rate = 0.3
96
+ attention_probs_dropout_prob = 0.1 # Changed from 0.4 to 0.1 to match config_29th.json
97
+ hidden_dropout_prob = 0.1
98
+ wd = 0.
99
+ pos_weight = 10
100
+ n_epochs = 5
101
+
102
+ use_amp = True
103
+
104
+ # %% [markdown]
105
+ # # Data
106
+
107
+ # %%
108
+ from sklearn.model_selection import train_test_split
109
+
110
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
111
+ for sample in data:
112
+ if len(set(sample['labels'])) > 1:
113
+ sample['is_pos'] = 1
114
+ else:
115
+ sample['is_pos'] = 0
116
+ print("original datapoints: ", len(data))
117
+
118
+
119
+ # moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
120
+ # print("moredata datapoints: ", len(moredata))
121
+
122
+
123
+ # %%
124
+ from sklearn.model_selection import GroupKFold
125
+ import random
126
+ random.seed(0)
127
+ kf = GroupKFold(n_splits=5)
128
+ folds = []
129
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
130
+ folds.append([data[i]['document'] for i in valid_index])
131
+
132
+ # %%
133
+ # Define simplified labels
134
+ pii_types = [
135
+ 'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
136
+ 'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
137
+ ]
138
+ all_labels_simplified = sorted(pii_types + ['O'])
139
+ label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
140
+ id2label_simplified = {v: k for k, v in label2id_simplified.items()}
141
+
142
+ # Original target BIO labels
143
+ original_target_bio = sorted([
144
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
145
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
146
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
147
+ 'O' # Ensure 'O' is part of the original set as well
148
+ ])
149
+ # Add I-EMAIL, I-USERNAME if they are possible, though not in the provided list.
150
+ # For now, sticking to the provided original_target_bio and adding 'O'.
151
+ # Ensure 'O' is consistently handled.
152
+ if 'O' not in original_target_bio:
153
+ original_target_bio_with_o = sorted(list(set(original_target_bio + ['O'])))
154
+ else:
155
+ original_target_bio_with_o = sorted(list(set(original_target_bio)))
156
+
157
+ label2id_original_bio = {l: i for i, l in enumerate(original_target_bio_with_o)}
158
+ id2label_original_bio = {v: k for k, v in label2id_original_bio.items()}
159
+
160
+
161
+ if USE_SIMPLIFIED_LABELS:
162
+ all_labels = all_labels_simplified
163
+ label2id = label2id_simplified
164
+ id2label = id2label_simplified
165
+ print(f"Using SIMPLIFIED labels ({len(all_labels)}): {id2label}")
166
+ else:
167
+ all_labels = original_target_bio_with_o
168
+ label2id = label2id_original_bio
169
+ id2label = id2label_original_bio
170
+ print(f"Using ORIGINAL BIO labels ({len(all_labels)}): {id2label}")
171
+
172
+
173
+ # %% [markdown]
174
+ # # Dataset
175
+
176
+ # %%
177
+ def tokenize(example, tokenizer, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified flag
178
+
179
+ # rebuild text from tokens
180
+ text = []
181
+ char_labels = []
182
+
183
+ for t, l, ws in zip(
184
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
185
+ ):
186
+ text.append(t)
187
+ current_label = l
188
+ if use_simplified and l != "O":
189
+ current_label = l[2:] # Strip "B-" or "I-"
190
+
191
+ # Ensure current_label is in the target label set (either simplified or original BIO)
192
+ # This is a safeguard. If a label from data isn't in the defined 'all_labels', map it to 'O'.
193
+ if current_label not in current_label2id:
194
+ # This case should ideally not happen if data labels are consistent with chosen scheme.
195
+ # For BIO, 'l' itself should be in label2id. For simplified, l[2:] should be.
196
+ # print(f"Warning: Label \'{current_label}\' (from original \'{l}\') not in target label set. Mapping to 'O'.")
197
+ current_label = "O"
198
+
199
+ char_labels.extend([current_label] * len(t))
200
+
201
+ if ws:
202
+ text.append(" ")
203
+ char_labels.append("O") # Whitespace is always "O"
204
+
205
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
206
+ # Truncation strategy
207
+ if len(tokenized['input_ids']) > max_length:
208
+ # Implement the "first N/2 + last N/2" truncation strategy
209
+ first_half_len = max_length // 2
210
+ second_half_len = max_length - first_half_len # Ensures total length is max_length
211
+
212
+ keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
213
+ for key in keys_to_truncate:
214
+ if key in tokenized:
215
+ original_list = tokenized[key]
216
+ # This slicing takes the first 'first_half_len' elements and the last 'second_half_len' elements
217
+ tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
218
+ # Consider adding a warning if a key is expected but not found, though unlikely for these standard keys.
219
+
220
+ char_labels_arr = np.array(char_labels) # This remains after truncation, using the original full char_labels
221
+
222
+ text_str = "".join(text) # Renamed from 'text' to avoid conflict
223
+ token_labels = []
224
+
225
+ # Access offset_mapping using dictionary style access as tokenized might be a dict
226
+ current_offset_mapping = tokenized["offset_mapping"]
227
+
228
+ for start_idx, end_idx in current_offset_mapping:
229
+ # Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
230
+ # or any other token that doesn't map to actual characters in the original text.
231
+ if start_idx == end_idx:
232
+ token_labels.append(current_label2id["O"]) # Assign 'O' to special/empty tokens
233
+ continue
234
+
235
+ # Ensure indices are within the bounds of char_labels_arr and form a valid span
236
+ # end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
237
+ if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
238
+ token_labels.append(current_label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
239
+ continue
240
+
241
+ char_span_labels = char_labels_arr[start_idx:end_idx]
242
+
243
+ # If char_span_labels is somehow empty (e.g. if previous check was less strict)
244
+ if not char_span_labels.size:
245
+ token_labels.append(current_label2id["O"])
246
+ continue
247
+
248
+ unique_labels, counts = np.unique(char_span_labels, return_counts=True)
249
+
250
+ pii_labels_present_in_token = []
251
+ pii_counts_present_in_token = []
252
+
253
+ for lbl, count in zip(unique_labels, counts): # lbl is already simplified or original based on earlier logic
254
+ if lbl != "O":
255
+ pii_labels_present_in_token.append(lbl)
256
+ pii_counts_present_in_token.append(count)
257
+
258
+ if pii_labels_present_in_token:
259
+ # If PII labels are present, choose the most frequent PII label
260
+ most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
261
+ most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
262
+ token_labels.append(current_label2id[most_frequent_pii_label])
263
+ else:
264
+ # If only \'O\' is present or no PII labels found in the span
265
+ token_labels.append(current_label2id["O"])
266
+
267
+ length = len(tokenized["input_ids"]) # Use dictionary-style access
268
+
269
+ return {**tokenized, "labels": token_labels, "length": length}
270
+
271
+ # %%
272
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
273
+
274
+ class PIIDataset(Dataset):
275
+ def __init__(self, tokenizer, data, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified
276
+ self.tokenizer = tokenizer
277
+ self.data = copy.deepcopy(data)
278
+ self.current_label2id = current_label2id # Store the active label2id
279
+ self.max_length = max_length
280
+ self.use_simplified = use_simplified
281
+
282
+ for i in tqdm(range(len(self.data))):
283
+ if self.data[i].get('input_ids') is None:
284
+ self.data[i]['provided_labels'] = self.data[i]['labels']
285
+ self.data[i] = tokenize(
286
+ self.data[i],
287
+ self.tokenizer,
288
+ self.current_label2id, # Pass the active label2id
289
+ self.max_length,
290
+ self.use_simplified # Pass the flag
291
+ )
292
+
293
+ def __len__(self):
294
+ return len(self.data)
295
+
296
+ def __getitem__(self, idx):
297
+ return self.data[idx]
298
+
299
+
300
+ def collate_fn(batch):
301
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
302
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
303
+ labels = [torch.tensor(x['labels']) for x in batch]
304
+
305
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
306
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
307
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
308
+
309
+ return {
310
+ 'input_ids': input_ids_padded,
311
+ 'attention_mask': attention_masks_padded,
312
+ 'labels': labels_padded,
313
+ }
314
+
315
+ # Test with simplified labels
316
+ # The global label2id and USE_SIMPLIFIED_LABELS will be used here by PIIDataset
317
+ ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
318
+ print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
319
+ print(f"Example tokenized labels: {ds[9]['labels']}")
320
+
321
+
322
+ # %% [markdown]
323
+ # # Model
324
+
325
+ # %%
326
+ model = AutoModelForTokenClassification.from_pretrained(
327
+ backbone,
328
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
329
+ hidden_dropout_prob=hidden_dropout_prob,
330
+ num_labels=len(all_labels), # Use length of active labels
331
+ id2label=id2label, # Use active id2label
332
+ label2id=label2id, # Use active label2id
333
+ ignore_mismatched_sizes=True
334
+ )
335
+
336
+ # %% [markdown]
337
+ # # Loss
338
+
339
+ # %%
340
+ # Adjust weights for the active label set
341
+ weights = torch.ones(len(all_labels)) * pos_weight
342
+ if 'O' in label2id:
343
+ weights[label2id['O']] = 1
344
+ else:
345
+ print(f"Warning: 'O' label not found in active label2id for weight assignment: {label2id}. Assuming last index for 'O'.")
346
+ weights[-1] = 1
347
+
348
+
349
+ CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100) # Use general CE
350
+ def criterion(logits, labels):
351
+ # logits shape: (batch_size, seq_len, num_active_labels)
352
+ # labels shape: (batch_size, seq_len)
353
+ return CE(logits.view(-1, len(all_labels)), labels.view(-1)) # Use len(all_labels)
354
+
355
+ # %% [markdown]
356
+ # # Metric
357
+
358
+ # %%
359
+ def compute_metrics(p, current_all_labels_list, th=0.5): # Pass the active list of all label strings
360
+ predictions, labels = p
361
+
362
+ o_label_str = "O"
363
+ o_label_idx = -1
364
+ # current_all_labels_list is the list of string names for labels, e.g., all_labels
365
+ for i, lbl_str in enumerate(current_all_labels_list):
366
+ if lbl_str == o_label_str:
367
+ o_label_idx = i
368
+ break
369
+ if o_label_idx == -1:
370
+ print(f"Warning: Could not find 'O' label in {current_all_labels_list} for metric thresholding. Using last index.")
371
+ o_label_idx = len(current_all_labels_list) - 1
372
+
373
+
374
+ true_predictions = []
375
+ true_labels = []
376
+ for tmp, batch_l in zip(predictions, labels):
377
+ batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy()
378
+
379
+ o_column_probs_slice = batch_p_probs[:, :, o_label_idx]
380
+
381
+ condition_mask = o_column_probs_slice < th
382
+
383
+ o_column_probs_slice[condition_mask] = -9999
384
+
385
+ batch_p_final_ids = np.argmax(batch_p_probs, axis=-1)
386
+
387
+ true_predictions.extend([current_all_labels_list[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
388
+ true_labels.extend([current_all_labels_list[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
389
+
390
+ # Using seqeval for P, R, F1 (F5)
391
+ # seqeval expects lists of lists of strings.
392
+ # Here true_labels and true_predictions are flat lists.
393
+ # For proper seqeval, we'd need to group them back by sequence.
394
+ # However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
395
+ # Let's keep that structure for now, as it's what the script used for its internal F5.
396
+ # Note: This F5 is on simplified labels, not BIO.
397
+
398
+ recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
399
+ precision = precision_score([true_labels], [true_predictions], average='micro')
400
+ # Calculate F5 score manually based on P and R
401
+ beta = 5
402
+ f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
403
+ if precision + recall == 0: # Handle case where P and R are both zero
404
+ f5_score = 0.0
405
+
406
+ results = {
407
+ 'recall': recall,
408
+ 'precision': precision,
409
+ 'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
410
+ }
411
+ return results
412
+
413
+
414
+ # %% [markdown]
415
+ # # Train & Valid Func
416
+
417
+ # %%
418
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
419
+ model.train()
420
+ running_loss = []
421
+ bar = tqdm(loader, total=len(loader))
422
+ for batch in bar:
423
+ optimizer.zero_grad()
424
+
425
+ input_ids = batch['input_ids'].to(device)
426
+ attention_mask = batch['attention_mask'].to(device)
427
+ labels = batch['labels'].to(device)
428
+
429
+ if scaler is not None:
430
+ with torch.cuda.amp.autocast():
431
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
432
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
433
+ scaler.scale(loss).backward()
434
+ scaler.step(optimizer)
435
+ scaler.update()
436
+ else:
437
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
438
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
439
+ loss.backward()
440
+ optimizer.step()
441
+
442
+ scheduler.step() # Moved scheduler step here, common practice per batch
443
+ running_loss.append(loss.item())
444
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
445
+
446
+ return np.mean(running_loss)
447
+
448
+
449
+ def valid_one_epoch(model, loader):
450
+ model.eval()
451
+ running_loss = []
452
+ bar = tqdm(loader, total=len(loader))
453
+ predictions_logits = []
454
+ true_labels_list = []
455
+
456
+ with torch.no_grad():
457
+ for batch in bar:
458
+ input_ids = batch['input_ids'].to(device)
459
+ attention_mask = batch['attention_mask'].to(device)
460
+ labels_batch = batch['labels'].to(device) # Renamed to avoid conflict
461
+
462
+ outputs = model(input_ids, attention_mask=attention_mask)
463
+ loss = criterion(outputs.logits, labels_batch) # Use labels_batch
464
+ running_loss.append(loss.item())
465
+
466
+ # Store logits and true labels for metric calculation
467
+ predictions_logits.append(outputs.logits.cpu().numpy())
468
+ true_labels_list.append(labels_batch.cpu().numpy()) # Use labels_batch
469
+
470
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
471
+
472
+ # Pass the active all_labels (list of strings) to compute_metrics
473
+ metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels)
474
+ return np.mean(running_loss), metrics_results
475
+
476
+ # %% [markdown]
477
+ # # Scheduler
478
+
479
+ # %%
480
+ rcParams['figure.figsize'] = 20, 2
481
+
482
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
483
+ return get_cosine_schedule_with_warmup(
484
+ optimizer,
485
+ int(num_training_steps * warmup_steps_ratio),
486
+ num_training_steps
487
+ )
488
+
489
+ opt = AdamW(model.parameters(), lr=lr)
490
+ sch = get_scheduler(opt, 100, 0.1)
491
+
492
+ lrs = []
493
+ for epoch in range(1, 100+1):
494
+ sch.step()
495
+ lrs.append(opt.param_groups[0]["lr"])
496
+
497
+ plt.plot(range(len(lrs)), lrs)
498
+
499
+ # %% [markdown]
500
+ # # Run
501
+
502
+ # %%
503
+ def run(fold,model_to_train):
504
+
505
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt') # exp_name is now dynamic
506
+ # Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
507
+ model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf') # exp_name is now dynamic
508
+ model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf') # exp_name is now dynamic
509
+
510
+ # Create directories if they don't exist
511
+ os.makedirs(model_save_dir_best, exist_ok=True)
512
+ os.makedirs(model_save_dir_last, exist_ok=True)
513
+ # Path for optimizer and other states (still using torch.save for these with custom loop)
514
+ optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
515
+
516
+
517
+ valid_doc_ids = folds[fold]
518
+ # train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
519
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
520
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
521
+
522
+ # Use active label2id and USE_SIMPLIFIED_LABELS flag
523
+ dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
524
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
525
+ print(len(dataset_train), len(dataset_valid))
526
+
527
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
528
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
529
+
530
+
531
+ # model = get_model() # Original script had this, but model is passed now
532
+ current_model = model_to_train.to(device) # Use the passed model
533
+
534
+ optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
535
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
536
+ from_epoch = 0 # Assuming fresh train, not resuming
537
+ metric_best = 0
538
+
539
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
540
+
541
+ for epoch in range(1, n_epochs+1):
542
+ # if epoch < from_epoch + 1:
543
+ # print(logs[epoch-1]) # Assuming logs is not used for fresh train
544
+ # continue
545
+
546
+ print(time.ctime(), 'Epoch:', epoch)
547
+
548
+ train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
549
+ valid_loss, result = valid_one_epoch(current_model, loader_valid)
550
+ metric = result["f1"] # This is F5 score on simplified labels
551
+
552
+ content = time.ctime() + ' ' + \
553
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
554
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
555
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
556
+ print(content)
557
+ with open(log_file, 'a') as appender:
558
+ appender.write(content + '\n')
559
+
560
+ if metric > metric_best:
561
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
562
+ if not DEBUG:
563
+ current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
564
+ tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
565
+ metric_best = metric
566
+
567
+ # Save Last states (model, optimizer, scheduler)
568
+ if not DEBUG:
569
+ current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
570
+ tokenizer.save_pretrained(model_save_dir_last)
571
+ torch.save(
572
+ {
573
+ 'epoch': epoch,
574
+ # model_state_dict is not needed if using save_pretrained for the model itself
575
+ 'optimizer_state_dict': optimizer.state_dict(),
576
+ 'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
577
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
578
+ 'metric_best': metric_best,
579
+ },
580
+ optimizer_etc_file_last # Save optimizer and other states separately
581
+ )
582
+
583
+ del current_model # Use current_model
584
+ torch.cuda.empty_cache()
585
+ gc.collect()
586
+
587
+ # %%
588
+ # model is defined globally from "Model" cell. Pass it to run.
589
+ run(0, model)
590
+
591
+ # %%
592
+
593
+
594
+
code/training-single-model-v8.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
3
+ #
4
+ # # Introduction
5
+ #
6
+ # Hi all,
7
+ #
8
+ # In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
9
+ #
10
+ # Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
11
+ #
12
+ # Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
13
+ #
14
+ # Let's dive into the details and uncover the techniques that led to a successful submission!
15
+ #
16
+ # [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
17
+ #
18
+ # # Summary
19
+ #
20
+ # * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
21
+ # * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
22
+ # * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
23
+ # * For inference, no truncation.
24
+ # * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
25
+ # * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
26
+ # * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
27
+ # * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
28
+ # * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
29
+ # * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
30
+ # * cosine with warmup learning rate schedule -- almost became a standard setting.
31
+ # * warmup 0.5 epo
32
+ # * cosine 4.5 epo
33
+ #
34
+ # # Acknowledge
35
+ #
36
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
37
+ #
38
+ # For training a single model this notebook shows, it takes less than 40min!
39
+ #
40
+
41
+ # %%
42
+ import os
43
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
44
+ import warnings
45
+ warnings.filterwarnings("ignore")
46
+ DEBUG=False
47
+
48
+ # %%
49
+ # !pip install -q seqeval
50
+
51
+ # %%
52
+ import gc
53
+ import json
54
+ import copy
55
+ import time
56
+ import pickle
57
+ import numpy as np
58
+ import pandas as pd
59
+ from itertools import chain
60
+ from transformers import AutoTokenizer
61
+ from transformers import AutoModelForTokenClassification
62
+ from transformers import get_cosine_schedule_with_warmup
63
+ from tqdm import tqdm
64
+
65
+ from seqeval.metrics import recall_score, precision_score
66
+ from seqeval.metrics import f1_score
67
+
68
+ import torch
69
+ from torch import nn
70
+ from torch.utils.data import Dataset, DataLoader
71
+ from torch.nn.utils.rnn import pad_sequence
72
+ from torch.nn import CrossEntropyLoss
73
+ from torch.optim import AdamW
74
+
75
+ import matplotlib.pyplot as plt
76
+ from pylab import rcParams
77
+
78
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
79
+
80
+ # %% [markdown]
81
+ # # Config
82
+
83
+ # %%
84
+ exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_simplified_labels' # Updated exp_name
85
+
86
+ backbone = 'microsoft/deberta-v3-large'
87
+
88
+ TRAINING_MAX_LENGTH = 4096
89
+ batch_size = 2
90
+ lr = 1e-5
91
+ ds_rate = 0.3
92
+ attention_probs_dropout_prob = 0.4
93
+ hidden_dropout_prob = 0.1
94
+ wd = 0.
95
+ pos_weight = 10
96
+ n_epochs = 5
97
+
98
+ use_amp = True
99
+
100
+ # %% [markdown]
101
+ # # Data
102
+
103
+ # %%
104
+ from sklearn.model_selection import train_test_split
105
+
106
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
107
+ for sample in data:
108
+ if len(set(sample['labels'])) > 1:
109
+ sample['is_pos'] = 1
110
+ else:
111
+ sample['is_pos'] = 0
112
+ print("original datapoints: ", len(data))
113
+
114
+
115
+ # moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
116
+ # print("moredata datapoints: ", len(moredata))
117
+
118
+
119
+ # %%
120
+ from sklearn.model_selection import GroupKFold
121
+ import random
122
+ random.seed(0)
123
+ kf = GroupKFold(n_splits=5)
124
+ folds = []
125
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
126
+ folds.append([data[i]['document'] for i in valid_index])
127
+
128
+ # %%
129
+ # Define simplified labels
130
+ pii_types = [
131
+ 'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
132
+ 'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
133
+ ]
134
+ # all_labels_simplified will be pii_types + ['O']
135
+ all_labels_simplified = sorted(pii_types + ['O'])
136
+ label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
137
+ id2label_simplified = {v: k for k, v in label2id_simplified.items()}
138
+
139
+ # Original target for reference or other logic if needed, but not for model head
140
+ original_target_bio = [
141
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
142
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
143
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
144
+ ]
145
+
146
+ print(f"Simplified labels ({len(all_labels_simplified)}): {id2label_simplified}")
147
+
148
+ # %% [markdown]
149
+ # # Dataset
150
+
151
+ # %%
152
+ def tokenize(example, tokenizer, label2id, max_length): # label2id here will be label2id_simplified
153
+
154
+ # rebuild text from tokens
155
+ text = []
156
+ char_labels = [] # Renamed from 'labels' to avoid confusion with example['provided_labels']
157
+
158
+ for t, l, ws in zip(
159
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
160
+ ):
161
+ text.append(t)
162
+ # Simplify label: strip B-/I- prefix if it's a PII label
163
+ current_label = l
164
+ if l != "O":
165
+ current_label = l[2:] # Strip "B-" or "I-"
166
+ char_labels.extend([current_label] * len(t))
167
+
168
+ if ws:
169
+ text.append(" ")
170
+ char_labels.append("O") # Whitespace is always "O"
171
+
172
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
173
+ # Truncation strategy
174
+ if len(tokenized['input_ids']) > max_length:
175
+ # Truncate by taking first max_length//2 and last max_length//2 tokens
176
+ # This strategy is mentioned in the original notebook's markdown for inputs longer than max_length
177
+ # For 'offset_mapping', we need to be careful. If we truncate input_ids, we must truncate offset_mapping too.
178
+ # The original code did this:
179
+ # for key in tokenized.keys():
180
+ # tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
181
+ # This is complex for offset_mapping as the second half's offsets are relative to the original string.
182
+ # A simpler truncation for now, or ensure max_length is sufficient.
183
+ # For simplicity, let's use Hugging Face's truncation, but it needs to be enabled in tokenizer call if not already.
184
+ # The original code had truncation=False then a manual truncation.
185
+ # Let's stick to the original manual truncation for input_ids and attention_mask,
186
+ # and apply it carefully to offset_mapping.
187
+ # However, the provided code snippet for tokenize in the original script does:
188
+ # tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
189
+ # if len(tokenized['input_ids']) > max_length:
190
+ # for key in tokenized.keys():
191
+ # tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
192
+ # This manual truncation is applied *after* tokenization.
193
+ # Let's re-enable truncation in the tokenizer call for safety, though the manual one might override.
194
+ # For this modification, we'll assume the existing truncation logic is what the user wants to keep.
195
+ # The key change is how labels are derived.
196
+
197
+ # Re-applying the original truncation logic if input_ids are too long
198
+ if len(tokenized['input_ids']) > max_length:
199
+ first_half_len = max_length // 2
200
+ second_half_start = len(tokenized['input_ids']) - (max_length - first_half_len) # Ensure total is max_length
201
+
202
+ new_tokenized = {}
203
+ for key in tokenized.keys():
204
+ if key == 'offset_mapping': # Special handling for offset_mapping if needed, or just truncate
205
+ new_tokenized[key] = tokenized[key][:first_half_len] + tokenized[key][second_half_start:]
206
+ else:
207
+ new_tokenized[key] = tokenized[key][:first_half_len] + tokenized[key][second_half_start:]
208
+ tokenized = new_tokenized
209
+
210
+
211
+ char_labels_arr = np.array(char_labels) # Use the simplified char_labels
212
+
213
+ text_str = "".join(text) # Renamed from 'text' to avoid conflict
214
+ token_labels = []
215
+
216
+ # Access offset_mapping using dictionary style access as tokenized might be a dict
217
+ current_offset_mapping = tokenized["offset_mapping"]
218
+
219
+ for start_idx, end_idx in current_offset_mapping:
220
+ # CLS token
221
+ if start_idx == 0 and end_idx == 0:
222
+ token_labels.append(label2id["O"]) # Use the passed simplified label2id
223
+ continue
224
+
225
+ # case when token starts with whitespace
226
+ # Ensure start_idx is within bounds of text_str
227
+ if start_idx < len(text_str) and text_str[start_idx].isspace():
228
+ start_idx += 1
229
+
230
+ # Ensure start_idx is within bounds of char_labels_arr after potential increment
231
+ # And handle cases where start_idx might become >= len(char_labels_arr) due to offsets near end
232
+ if start_idx >= len(char_labels_arr):
233
+ # This can happen if offset_mapping points beyond the original text due to truncation or special tokens
234
+ # A safe fallback is 'O' or to ensure char_labels_arr is long enough / handle offsets carefully
235
+ # Given the original code's `while start_idx >= len(labels): start_idx -= 1`, let's adapt that.
236
+ current_char_label_idx = min(start_idx, len(char_labels_arr) - 1)
237
+ else:
238
+ current_char_label_idx = start_idx
239
+
240
+ token_labels.append(label2id[char_labels_arr[current_char_label_idx]])
241
+
242
+ length = len(tokenized["input_ids"]) # Use dictionary-style access
243
+
244
+ return {**tokenized, "labels": token_labels, "length": length}
245
+
246
+ # %%
247
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
248
+
249
+ class PIIDataset(Dataset):
250
+ def __init__(self, tokenizer, data, label2id, max_length): # label2id will be simplified
251
+ self.tokenizer = tokenizer
252
+ self.data = copy.deepcopy(data)
253
+ self.label2id = label2id
254
+ self.max_length = max_length
255
+
256
+ for i in tqdm(range(len(self.data))):
257
+ if self.data[i].get('input_ids') is None:
258
+ self.data[i]['provided_labels'] = self.data[i]['labels'] # Keep original BIO labels here for tokenize
259
+ self.data[i] = tokenize(
260
+ self.data[i],
261
+ self.tokenizer,
262
+ self.label2id, # Pass simplified label2id
263
+ self.max_length
264
+ )
265
+
266
+ def __len__(self):
267
+ return len(self.data)
268
+
269
+ def __getitem__(self, idx):
270
+ return self.data[idx]
271
+
272
+
273
+ def collate_fn(batch):
274
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
275
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
276
+ labels = [torch.tensor(x['labels']) for x in batch]
277
+
278
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
279
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
280
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
281
+
282
+ return {
283
+ 'input_ids': input_ids_padded,
284
+ 'attention_mask': attention_masks_padded,
285
+ 'labels': labels_padded,
286
+ }
287
+
288
+ # Test with simplified labels
289
+ ds = PIIDataset(tokenizer, data[:20], label2id_simplified, TRAINING_MAX_LENGTH)
290
+ print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
291
+ print(f"Example tokenized labels: {ds[9]['labels']}")
292
+
293
+
294
+ # %% [markdown]
295
+ # # Model
296
+
297
+ # %%
298
+ model = AutoModelForTokenClassification.from_pretrained(
299
+ backbone,
300
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
301
+ hidden_dropout_prob=hidden_dropout_prob,
302
+ num_labels=len(all_labels_simplified), # Use length of simplified labels (8)
303
+ id2label=id2label_simplified, # Use simplified id2label
304
+ label2id=label2id_simplified, # Use simplified label2id
305
+ ignore_mismatched_sizes=True # Good practice if changing head
306
+ )
307
+
308
+ # %% [markdown]
309
+ # # Loss
310
+
311
+ # %%
312
+ # Adjust weights for simplified labels
313
+ weights_simplified = torch.ones(len(all_labels_simplified)) * pos_weight
314
+ # Assuming 'O' is the label for non-PII and its ID is known via label2id_simplified
315
+ if 'O' in label2id_simplified:
316
+ weights_simplified[label2id_simplified['O']] = 1
317
+ else:
318
+ # Fallback if 'O' is not named exactly like that, though it should be.
319
+ # This assumes 'O' is the last class if not found by name.
320
+ print("Warning: 'O' label not found in label2id_simplified for weight assignment. Assuming last index for 'O'.")
321
+ weights_simplified[-1] = 1
322
+
323
+
324
+ CE_simplified = nn.CrossEntropyLoss(weight=weights_simplified.to(device), ignore_index=-100)
325
+ def criterion(logits, labels):
326
+ # logits shape: (batch_size, seq_len, num_simplified_labels)
327
+ # labels shape: (batch_size, seq_len)
328
+ return CE_simplified(logits.view(-1, len(all_labels_simplified)), labels.view(-1))
329
+
330
+ # %% [markdown]
331
+ # # Metric
332
+
333
+ # %%
334
+ def compute_metrics(p, current_all_labels, th=0.5): # Pass simplified all_labels
335
+ predictions, labels = p # predictions are logits
336
+
337
+ # Determine index of 'O' label in the current_all_labels (simplified)
338
+ o_label_str = "O" # Assuming 'O' is always named this way
339
+ o_label_idx = -1
340
+ for i, lbl_str in enumerate(current_all_labels): # current_all_labels is a list of strings like ['EMAIL', ..., 'O']
341
+ if lbl_str == o_label_str:
342
+ o_label_idx = i
343
+ break
344
+ if o_label_idx == -1:
345
+ print(f"Warning: Could not find 'O' label in {current_all_labels} for metric thresholding. Using last index.")
346
+ o_label_idx = len(current_all_labels) - 1
347
+
348
+
349
+ true_predictions = []
350
+ true_labels = []
351
+ for tmp, batch_l in zip(predictions, labels): # tmp contains logits for a batch item
352
+ batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy() # Convert logits to probs
353
+
354
+ # Get a view of the 'O' column probabilities
355
+ # batch_p_probs shape is (batch_size, seq_len, num_simplified_labels)
356
+ o_column_probs_slice = batch_p_probs[:, :, o_label_idx] # Shape (batch_size, seq_len)
357
+
358
+ # Create the boolean mask based on this slice
359
+ condition_mask = o_column_probs_slice < th # Shape (batch_size, seq_len)
360
+
361
+ # Assign to the 'O' column slice using this mask. This modifies batch_p_probs in place.
362
+ o_column_probs_slice[condition_mask] = -9999
363
+
364
+ batch_p_final_ids = np.argmax(batch_p_probs, axis=-1) # Get final predicted IDs
365
+
366
+ true_predictions.extend([current_all_labels[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
367
+ true_labels.extend([current_all_labels[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
368
+
369
+ # Using seqeval for P, R, F1 (F5)
370
+ # seqeval expects lists of lists of strings.
371
+ # Here true_labels and true_predictions are flat lists.
372
+ # For proper seqeval, we'd need to group them back by sequence.
373
+ # However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
374
+ # Let's keep that structure for now, as it's what the script used for its internal F5.
375
+ # Note: This F5 is on simplified labels, not BIO.
376
+
377
+ recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
378
+ precision = precision_score([true_labels], [true_predictions], average='micro')
379
+ # Calculate F5 score manually based on P and R
380
+ beta = 5
381
+ f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
382
+ if precision + recall == 0: # Handle case where P and R are both zero
383
+ f5_score = 0.0
384
+
385
+ results = {
386
+ 'recall': recall,
387
+ 'precision': precision,
388
+ 'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
389
+ }
390
+ return results
391
+
392
+
393
+ # %% [markdown]
394
+ # # Train & Valid Func
395
+
396
+ # %%
397
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
398
+ model.train()
399
+ running_loss = []
400
+ bar = tqdm(loader, total=len(loader))
401
+ for batch in bar:
402
+ optimizer.zero_grad()
403
+
404
+ input_ids = batch['input_ids'].to(device)
405
+ attention_mask = batch['attention_mask'].to(device)
406
+ labels = batch['labels'].to(device)
407
+
408
+ if scaler is not None:
409
+ with torch.cuda.amp.autocast():
410
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
411
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
412
+ scaler.scale(loss).backward()
413
+ scaler.step(optimizer)
414
+ scaler.update()
415
+ else:
416
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
417
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
418
+ loss.backward()
419
+ optimizer.step()
420
+
421
+ scheduler.step() # Moved scheduler step here, common practice per batch
422
+ running_loss.append(loss.item())
423
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
424
+
425
+ return np.mean(running_loss)
426
+
427
+
428
+ def valid_one_epoch(model, loader):
429
+ model.eval()
430
+ running_loss = []
431
+ bar = tqdm(loader, total=len(loader))
432
+ predictions_logits = [] # Store logits
433
+ true_labels_list = [] # Store true labels
434
+
435
+ with torch.no_grad():
436
+ for batch in bar:
437
+ input_ids = batch['input_ids'].to(device)
438
+ attention_mask = batch['attention_mask'].to(device)
439
+ labels_ = batch['labels'].to(device)
440
+
441
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
442
+ loss = criterion(outputs['logits'], labels_) # criterion uses simplified label count
443
+
444
+ running_loss.append(loss.item())
445
+ predictions_logits.append(outputs['logits'].detach().cpu().numpy())
446
+ true_labels_list.append(labels_.detach().cpu().numpy())
447
+
448
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
449
+
450
+ # Pass simplified all_labels_simplified to compute_metrics
451
+ metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels_simplified)
452
+ return np.mean(running_loss), metrics_results
453
+
454
+ # %% [markdown]
455
+ # # Scheduler
456
+
457
+ # %%
458
+ rcParams['figure.figsize'] = 20, 2
459
+
460
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
461
+ return get_cosine_schedule_with_warmup(
462
+ optimizer,
463
+ int(num_training_steps * warmup_steps_ratio),
464
+ num_training_steps
465
+ )
466
+
467
+ opt = AdamW(model.parameters(), lr=lr)
468
+ sch = get_scheduler(opt, 100, 0.1)
469
+
470
+ lrs = []
471
+ for epoch in range(1, 100+1):
472
+ sch.step()
473
+ lrs.append(opt.param_groups[0]["lr"])
474
+
475
+ plt.plot(range(len(lrs)), lrs)
476
+
477
+ # %% [markdown]
478
+ # # Run
479
+
480
+ # %%
481
+ def run(fold,model_to_train): # Renamed model to model_to_train to avoid conflict
482
+
483
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
484
+ # Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
485
+ model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
486
+ model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
487
+
488
+ # Create directories if they don't exist
489
+ os.makedirs(model_save_dir_best, exist_ok=True)
490
+ os.makedirs(model_save_dir_last, exist_ok=True)
491
+ # Path for optimizer and other states (still using torch.save for these with custom loop)
492
+ optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
493
+
494
+
495
+ valid_doc_ids = folds[fold]
496
+ # train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
497
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
498
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
499
+
500
+ # Use simplified label2id
501
+ dataset_train = PIIDataset(tokenizer, train_data, label2id_simplified, TRAINING_MAX_LENGTH)
502
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id_simplified, TRAINING_MAX_LENGTH)
503
+ print(len(dataset_train), len(dataset_valid))
504
+
505
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
506
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
507
+
508
+
509
+ # model = get_model() # Original script had this, but model is passed now
510
+ current_model = model_to_train.to(device) # Use the passed model
511
+
512
+ optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
513
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
514
+ from_epoch = 0 # Assuming fresh train, not resuming
515
+ metric_best = 0
516
+
517
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
518
+
519
+ for epoch in range(1, n_epochs+1):
520
+ # if epoch < from_epoch + 1:
521
+ # print(logs[epoch-1]) # Assuming logs is not used for fresh train
522
+ # continue
523
+
524
+ print(time.ctime(), 'Epoch:', epoch)
525
+
526
+ train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
527
+ valid_loss, result = valid_one_epoch(current_model, loader_valid)
528
+ metric = result["f1"] # This is F5 score on simplified labels
529
+
530
+ content = time.ctime() + ' ' + \
531
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
532
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
533
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
534
+ print(content)
535
+ with open(log_file, 'a') as appender:
536
+ appender.write(content + '\n')
537
+
538
+ if metric > metric_best:
539
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
540
+ if not DEBUG:
541
+ current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
542
+ tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
543
+ metric_best = metric
544
+
545
+ # Save Last states (model, optimizer, scheduler)
546
+ if not DEBUG:
547
+ current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
548
+ tokenizer.save_pretrained(model_save_dir_last)
549
+ torch.save(
550
+ {
551
+ 'epoch': epoch,
552
+ # model_state_dict is not needed if using save_pretrained for the model itself
553
+ 'optimizer_state_dict': optimizer.state_dict(),
554
+ 'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
555
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
556
+ 'metric_best': metric_best,
557
+ },
558
+ optimizer_etc_file_last # Save optimizer and other states separately
559
+ )
560
+
561
+ del current_model # Use current_model
562
+ torch.cuda.empty_cache()
563
+ gc.collect()
564
+
565
+ # %%
566
+ # model is defined globally from "Model" cell. Pass it to run.
567
+ run(0, model)
568
+
569
+ # %%
570
+
571
+
572
+
code/training-single-model-v9.py ADDED
@@ -0,0 +1,564 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
3
+ #
4
+ # # Introduction
5
+ #
6
+ # Hi all,
7
+ #
8
+ # In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
9
+ #
10
+ # Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
11
+ #
12
+ # Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
13
+ #
14
+ # Let's dive into the details and uncover the techniques that led to a successful submission!
15
+ #
16
+ # [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
17
+ #
18
+ # # Summary
19
+ #
20
+ # * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
21
+ # * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
22
+ # * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
23
+ # * For inference, no truncation.
24
+ # * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
25
+ # * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
26
+ # * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
27
+ # * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
28
+ # * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
29
+ # * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
30
+ # * cosine with warmup learning rate schedule -- almost became a standard setting.
31
+ # * warmup 0.5 epo
32
+ # * cosine 4.5 epo
33
+ #
34
+ # # Acknowledge
35
+ #
36
+ # I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
37
+ #
38
+ # For training a single model this notebook shows, it takes less than 40min!
39
+ #
40
+
41
+ # %%
42
+ import os
43
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
44
+ import warnings
45
+ warnings.filterwarnings("ignore")
46
+ DEBUG=False
47
+
48
+ # %%
49
+ # !pip install -q seqeval
50
+
51
+ # %%
52
+ import gc
53
+ import json
54
+ import copy
55
+ import time
56
+ import pickle
57
+ import numpy as np
58
+ import pandas as pd
59
+ from itertools import chain
60
+ from transformers import AutoTokenizer
61
+ from transformers import AutoModelForTokenClassification
62
+ from transformers import get_cosine_schedule_with_warmup
63
+ from tqdm import tqdm
64
+
65
+ from seqeval.metrics import recall_score, precision_score
66
+ from seqeval.metrics import f1_score
67
+
68
+ import torch
69
+ from torch import nn
70
+ from torch.utils.data import Dataset, DataLoader
71
+ from torch.nn.utils.rnn import pad_sequence
72
+ from torch.nn import CrossEntropyLoss
73
+ from torch.optim import AdamW
74
+
75
+ import matplotlib.pyplot as plt
76
+ from pylab import rcParams
77
+
78
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
79
+
80
+ # %% [markdown]
81
+ # # Config
82
+
83
+ # %%
84
+ exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_simplified_labels' # Updated exp_name
85
+
86
+ backbone = 'microsoft/deberta-v3-large'
87
+
88
+ TRAINING_MAX_LENGTH = 4096
89
+ batch_size = 2
90
+ lr = 1e-5
91
+ ds_rate = 0.3
92
+ attention_probs_dropout_prob = 0.4
93
+ hidden_dropout_prob = 0.1
94
+ wd = 0.
95
+ pos_weight = 10
96
+ n_epochs = 5
97
+
98
+ use_amp = True
99
+
100
+ # %% [markdown]
101
+ # # Data
102
+
103
+ # %%
104
+ from sklearn.model_selection import train_test_split
105
+
106
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
107
+ for sample in data:
108
+ if len(set(sample['labels'])) > 1:
109
+ sample['is_pos'] = 1
110
+ else:
111
+ sample['is_pos'] = 0
112
+ print("original datapoints: ", len(data))
113
+
114
+
115
+ # moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
116
+ # print("moredata datapoints: ", len(moredata))
117
+
118
+
119
+ # %%
120
+ from sklearn.model_selection import GroupKFold
121
+ import random
122
+ random.seed(0)
123
+ kf = GroupKFold(n_splits=5)
124
+ folds = []
125
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
126
+ folds.append([data[i]['document'] for i in valid_index])
127
+
128
+ # %%
129
+ # Define simplified labels
130
+ pii_types = [
131
+ 'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
132
+ 'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
133
+ ]
134
+ # all_labels_simplified will be pii_types + ['O']
135
+ all_labels_simplified = sorted(pii_types + ['O'])
136
+ label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
137
+ id2label_simplified = {v: k for k, v in label2id_simplified.items()}
138
+
139
+ # Original target for reference or other logic if needed, but not for model head
140
+ original_target_bio = [
141
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
142
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
143
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
144
+ ]
145
+
146
+ print(f"Simplified labels ({len(all_labels_simplified)}): {id2label_simplified}")
147
+
148
+ # %% [markdown]
149
+ # # Dataset
150
+
151
+ # %%
152
+ def tokenize(example, tokenizer, label2id, max_length): # label2id here will be label2id_simplified
153
+
154
+ # rebuild text from tokens
155
+ text = []
156
+ char_labels = [] # Renamed from 'labels' to avoid confusion with example['provided_labels']
157
+
158
+ for t, l, ws in zip(
159
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
160
+ ):
161
+ text.append(t)
162
+ # Simplify label: strip B-/I- prefix if it's a PII label
163
+ current_label = l
164
+ if l != "O":
165
+ current_label = l[2:] # Strip "B-" or "I-"
166
+ char_labels.extend([current_label] * len(t))
167
+
168
+ if ws:
169
+ text.append(" ")
170
+ char_labels.append("O") # Whitespace is always "O"
171
+
172
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
173
+ # Truncation strategy
174
+ if len(tokenized['input_ids']) > max_length:
175
+ # Implement the "first N/2 + last N/2" truncation strategy
176
+ first_half_len = max_length // 2
177
+ second_half_len = max_length - first_half_len # Ensures total length is max_length
178
+
179
+ keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
180
+ for key in keys_to_truncate:
181
+ if key in tokenized:
182
+ original_list = tokenized[key]
183
+ # This slicing takes the first 'first_half_len' elements and the last 'second_half_len' elements
184
+ tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
185
+ # Consider adding a warning if a key is expected but not found, though unlikely for these standard keys.
186
+
187
+ char_labels_arr = np.array(char_labels) # This remains after truncation, using the original full char_labels
188
+
189
+ text_str = "".join(text) # Renamed from 'text' to avoid conflict
190
+ token_labels = []
191
+
192
+ # Access offset_mapping using dictionary style access as tokenized might be a dict
193
+ current_offset_mapping = tokenized["offset_mapping"]
194
+
195
+ for start_idx, end_idx in current_offset_mapping:
196
+ # Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
197
+ # or any other token that doesn't map to actual characters in the original text.
198
+ if start_idx == end_idx:
199
+ token_labels.append(label2id["O"]) # Assign 'O' to special/empty tokens
200
+ continue
201
+
202
+ # Ensure indices are within the bounds of char_labels_arr and form a valid span
203
+ # end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
204
+ if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
205
+ token_labels.append(label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
206
+ continue
207
+
208
+ char_span_labels = char_labels_arr[start_idx:end_idx]
209
+
210
+ # If char_span_labels is somehow empty (e.g. if previous check was less strict)
211
+ if not char_span_labels.size:
212
+ token_labels.append(label2id["O"])
213
+ continue
214
+
215
+ unique_labels, counts = np.unique(char_span_labels, return_counts=True)
216
+
217
+ pii_labels_present_in_token = []
218
+ pii_counts_present_in_token = []
219
+
220
+ for label, count in zip(unique_labels, counts):
221
+ if label != "O": # Assuming "O" is the string for non-PII
222
+ pii_labels_present_in_token.append(label)
223
+ pii_counts_present_in_token.append(count)
224
+
225
+ if pii_labels_present_in_token:
226
+ # If PII labels are present, choose the most frequent PII label
227
+ most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
228
+ most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
229
+ token_labels.append(label2id[most_frequent_pii_label])
230
+ else:
231
+ # If only 'O' is present or no PII labels found in the span
232
+ token_labels.append(label2id["O"])
233
+
234
+ length = len(tokenized["input_ids"]) # Use dictionary-style access
235
+
236
+ return {**tokenized, "labels": token_labels, "length": length}
237
+
238
+ # %%
239
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
240
+
241
+ class PIIDataset(Dataset):
242
+ def __init__(self, tokenizer, data, label2id, max_length): # label2id will be simplified
243
+ self.tokenizer = tokenizer
244
+ self.data = copy.deepcopy(data)
245
+ self.label2id = label2id
246
+ self.max_length = max_length
247
+
248
+ for i in tqdm(range(len(self.data))):
249
+ if self.data[i].get('input_ids') is None:
250
+ self.data[i]['provided_labels'] = self.data[i]['labels'] # Keep original BIO labels here for tokenize
251
+ self.data[i] = tokenize(
252
+ self.data[i],
253
+ self.tokenizer,
254
+ self.label2id, # Pass simplified label2id
255
+ self.max_length
256
+ )
257
+
258
+ def __len__(self):
259
+ return len(self.data)
260
+
261
+ def __getitem__(self, idx):
262
+ return self.data[idx]
263
+
264
+
265
+ def collate_fn(batch):
266
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
267
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
268
+ labels = [torch.tensor(x['labels']) for x in batch]
269
+
270
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
271
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
272
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
273
+
274
+ return {
275
+ 'input_ids': input_ids_padded,
276
+ 'attention_mask': attention_masks_padded,
277
+ 'labels': labels_padded,
278
+ }
279
+
280
+ # Test with simplified labels
281
+ ds = PIIDataset(tokenizer, data[:20], label2id_simplified, TRAINING_MAX_LENGTH)
282
+ print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
283
+ print(f"Example tokenized labels: {ds[9]['labels']}")
284
+
285
+
286
+ # %% [markdown]
287
+ # # Model
288
+
289
+ # %%
290
+ model = AutoModelForTokenClassification.from_pretrained(
291
+ backbone,
292
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
293
+ hidden_dropout_prob=hidden_dropout_prob,
294
+ num_labels=len(all_labels_simplified), # Use length of simplified labels (8)
295
+ id2label=id2label_simplified, # Use simplified id2label
296
+ label2id=label2id_simplified, # Use simplified label2id
297
+ ignore_mismatched_sizes=True # Good practice if changing head
298
+ )
299
+
300
+ # %% [markdown]
301
+ # # Loss
302
+
303
+ # %%
304
+ # Adjust weights for simplified labels
305
+ weights_simplified = torch.ones(len(all_labels_simplified)) * pos_weight
306
+ # Assuming 'O' is the label for non-PII and its ID is known via label2id_simplified
307
+ if 'O' in label2id_simplified:
308
+ weights_simplified[label2id_simplified['O']] = 1
309
+ else:
310
+ # Fallback if 'O' is not named exactly like that, though it should be.
311
+ # This assumes 'O' is the last class if not found by name.
312
+ print("Warning: 'O' label not found in label2id_simplified for weight assignment. Assuming last index for 'O'.")
313
+ weights_simplified[-1] = 1
314
+
315
+
316
+ CE_simplified = nn.CrossEntropyLoss(weight=weights_simplified.to(device), ignore_index=-100)
317
+ def criterion(logits, labels):
318
+ # logits shape: (batch_size, seq_len, num_simplified_labels)
319
+ # labels shape: (batch_size, seq_len)
320
+ return CE_simplified(logits.view(-1, len(all_labels_simplified)), labels.view(-1))
321
+
322
+ # %% [markdown]
323
+ # # Metric
324
+
325
+ # %%
326
+ def compute_metrics(p, current_all_labels, th=0.5): # Pass simplified all_labels
327
+ predictions, labels = p # predictions are logits
328
+
329
+ # Determine index of 'O' label in the current_all_labels (simplified)
330
+ o_label_str = "O" # Assuming 'O' is always named this way
331
+ o_label_idx = -1
332
+ for i, lbl_str in enumerate(current_all_labels): # current_all_labels is a list of strings like ['EMAIL', ..., 'O']
333
+ if lbl_str == o_label_str:
334
+ o_label_idx = i
335
+ break
336
+ if o_label_idx == -1:
337
+ print(f"Warning: Could not find 'O' label in {current_all_labels} for metric thresholding. Using last index.")
338
+ o_label_idx = len(current_all_labels) - 1
339
+
340
+
341
+ true_predictions = []
342
+ true_labels = []
343
+ for tmp, batch_l in zip(predictions, labels): # tmp contains logits for a batch item
344
+ batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy() # Convert logits to probs
345
+
346
+ # Get a view of the 'O' column probabilities
347
+ # batch_p_probs shape is (batch_size, seq_len, num_simplified_labels)
348
+ o_column_probs_slice = batch_p_probs[:, :, o_label_idx] # Shape (batch_size, seq_len)
349
+
350
+ # Create the boolean mask based on this slice
351
+ condition_mask = o_column_probs_slice < th # Shape (batch_size, seq_len)
352
+
353
+ # Assign to the 'O' column slice using this mask. This modifies batch_p_probs in place.
354
+ o_column_probs_slice[condition_mask] = -9999
355
+
356
+ batch_p_final_ids = np.argmax(batch_p_probs, axis=-1) # Get final predicted IDs
357
+
358
+ true_predictions.extend([current_all_labels[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
359
+ true_labels.extend([current_all_labels[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
360
+
361
+ # Using seqeval for P, R, F1 (F5)
362
+ # seqeval expects lists of lists of strings.
363
+ # Here true_labels and true_predictions are flat lists.
364
+ # For proper seqeval, we'd need to group them back by sequence.
365
+ # However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
366
+ # Let's keep that structure for now, as it's what the script used for its internal F5.
367
+ # Note: This F5 is on simplified labels, not BIO.
368
+
369
+ recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
370
+ precision = precision_score([true_labels], [true_predictions], average='micro')
371
+ # Calculate F5 score manually based on P and R
372
+ beta = 5
373
+ f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
374
+ if precision + recall == 0: # Handle case where P and R are both zero
375
+ f5_score = 0.0
376
+
377
+ results = {
378
+ 'recall': recall,
379
+ 'precision': precision,
380
+ 'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
381
+ }
382
+ return results
383
+
384
+
385
+ # %% [markdown]
386
+ # # Train & Valid Func
387
+
388
+ # %%
389
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
390
+ model.train()
391
+ running_loss = []
392
+ bar = tqdm(loader, total=len(loader))
393
+ for batch in bar:
394
+ optimizer.zero_grad()
395
+
396
+ input_ids = batch['input_ids'].to(device)
397
+ attention_mask = batch['attention_mask'].to(device)
398
+ labels = batch['labels'].to(device)
399
+
400
+ if scaler is not None:
401
+ with torch.cuda.amp.autocast():
402
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
403
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
404
+ scaler.scale(loss).backward()
405
+ scaler.step(optimizer)
406
+ scaler.update()
407
+ else:
408
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
409
+ loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
410
+ loss.backward()
411
+ optimizer.step()
412
+
413
+ scheduler.step() # Moved scheduler step here, common practice per batch
414
+ running_loss.append(loss.item())
415
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
416
+
417
+ return np.mean(running_loss)
418
+
419
+
420
+ def valid_one_epoch(model, loader):
421
+ model.eval()
422
+ running_loss = []
423
+ bar = tqdm(loader, total=len(loader))
424
+ predictions_logits = [] # Store logits
425
+ true_labels_list = [] # Store true labels
426
+
427
+ with torch.no_grad():
428
+ for batch in bar:
429
+ input_ids = batch['input_ids'].to(device)
430
+ attention_mask = batch['attention_mask'].to(device)
431
+ labels_ = batch['labels'].to(device)
432
+
433
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
434
+ loss = criterion(outputs['logits'], labels_) # criterion uses simplified label count
435
+
436
+ running_loss.append(loss.item())
437
+ predictions_logits.append(outputs['logits'].detach().cpu().numpy())
438
+ true_labels_list.append(labels_.detach().cpu().numpy())
439
+
440
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
441
+
442
+ # Pass simplified all_labels_simplified to compute_metrics
443
+ metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels_simplified)
444
+ return np.mean(running_loss), metrics_results
445
+
446
+ # %% [markdown]
447
+ # # Scheduler
448
+
449
+ # %%
450
+ rcParams['figure.figsize'] = 20, 2
451
+
452
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
453
+ return get_cosine_schedule_with_warmup(
454
+ optimizer,
455
+ int(num_training_steps * warmup_steps_ratio),
456
+ num_training_steps
457
+ )
458
+
459
+ opt = AdamW(model.parameters(), lr=lr)
460
+ sch = get_scheduler(opt, 100, 0.1)
461
+
462
+ lrs = []
463
+ for epoch in range(1, 100+1):
464
+ sch.step()
465
+ lrs.append(opt.param_groups[0]["lr"])
466
+
467
+ plt.plot(range(len(lrs)), lrs)
468
+
469
+ # %% [markdown]
470
+ # # Run
471
+
472
+ # %%
473
+ def run(fold,model_to_train): # Renamed model to model_to_train to avoid conflict
474
+
475
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
476
+ # Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
477
+ model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
478
+ model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
479
+
480
+ # Create directories if they don't exist
481
+ os.makedirs(model_save_dir_best, exist_ok=True)
482
+ os.makedirs(model_save_dir_last, exist_ok=True)
483
+ # Path for optimizer and other states (still using torch.save for these with custom loop)
484
+ optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
485
+
486
+
487
+ valid_doc_ids = folds[fold]
488
+ # train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
489
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
490
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
491
+
492
+ # Use simplified label2id
493
+ dataset_train = PIIDataset(tokenizer, train_data, label2id_simplified, TRAINING_MAX_LENGTH)
494
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id_simplified, TRAINING_MAX_LENGTH)
495
+ print(len(dataset_train), len(dataset_valid))
496
+
497
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
498
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
499
+
500
+
501
+ # model = get_model() # Original script had this, but model is passed now
502
+ current_model = model_to_train.to(device) # Use the passed model
503
+
504
+ optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
505
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
506
+ from_epoch = 0 # Assuming fresh train, not resuming
507
+ metric_best = 0
508
+
509
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
510
+
511
+ for epoch in range(1, n_epochs+1):
512
+ # if epoch < from_epoch + 1:
513
+ # print(logs[epoch-1]) # Assuming logs is not used for fresh train
514
+ # continue
515
+
516
+ print(time.ctime(), 'Epoch:', epoch)
517
+
518
+ train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
519
+ valid_loss, result = valid_one_epoch(current_model, loader_valid)
520
+ metric = result["f1"] # This is F5 score on simplified labels
521
+
522
+ content = time.ctime() + ' ' + \
523
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
524
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
525
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
526
+ print(content)
527
+ with open(log_file, 'a') as appender:
528
+ appender.write(content + '\n')
529
+
530
+ if metric > metric_best:
531
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
532
+ if not DEBUG:
533
+ current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
534
+ tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
535
+ metric_best = metric
536
+
537
+ # Save Last states (model, optimizer, scheduler)
538
+ if not DEBUG:
539
+ current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
540
+ tokenizer.save_pretrained(model_save_dir_last)
541
+ torch.save(
542
+ {
543
+ 'epoch': epoch,
544
+ # model_state_dict is not needed if using save_pretrained for the model itself
545
+ 'optimizer_state_dict': optimizer.state_dict(),
546
+ 'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
547
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
548
+ 'metric_best': metric_best,
549
+ },
550
+ optimizer_etc_file_last # Save optimizer and other states separately
551
+ )
552
+
553
+ del current_model # Use current_model
554
+ torch.cuda.empty_cache()
555
+ gc.collect()
556
+
557
+ # %%
558
+ # model is defined globally from "Model" cell. Pass it to run.
559
+ run(0, model)
560
+
561
+ # %%
562
+
563
+
564
+
code/training-subm-moredata.py ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # %%
3
+ import os
4
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
5
+ import warnings
6
+ warnings.filterwarnings("ignore")
7
+ DEBUG=False
8
+
9
+ USE_SIMPLIFIED_LABELS = True # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS
10
+
11
+ # %%
12
+ # !pip install -q seqeval
13
+
14
+ # %%
15
+ import gc
16
+ import json
17
+ import copy
18
+ import time
19
+ import pickle
20
+ import numpy as np
21
+ import pandas as pd
22
+ from itertools import chain
23
+ from transformers import AutoTokenizer
24
+ from transformers import AutoModelForTokenClassification
25
+ from transformers import get_cosine_schedule_with_warmup
26
+ from tqdm import tqdm
27
+
28
+ from seqeval.metrics import recall_score, precision_score
29
+ from seqeval.metrics import f1_score
30
+
31
+ import torch
32
+ from torch import nn
33
+ from torch.utils.data import Dataset, DataLoader
34
+ from torch.nn.utils.rnn import pad_sequence
35
+ from torch.nn import CrossEntropyLoss
36
+ from torch.optim import AdamW
37
+
38
+ import matplotlib.pyplot as plt
39
+ from pylab import rcParams
40
+
41
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
42
+
43
+ # %% [markdown]
44
+ # # Config
45
+
46
+ # %%
47
+ exp_name_base = 'deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo' # Changed ado4 to ado1
48
+ exp_name = f"{exp_name_base}_{'simplified_labels' if USE_SIMPLIFIED_LABELS else 'original_bio_labels'}"
49
+
50
+
51
+ backbone = 'microsoft/deberta-v3-large'
52
+
53
+ TRAINING_MAX_LENGTH = 4096
54
+ batch_size = 2
55
+ lr = 1e-5
56
+ ds_rate = 0.3
57
+ attention_probs_dropout_prob = 0.4
58
+ hidden_dropout_prob = 0.1
59
+ wd = 0.
60
+ pos_weight = 10
61
+ n_epochs = 5
62
+
63
+ use_amp = True
64
+
65
+ # %% [markdown]
66
+ # # Data
67
+
68
+ # %%
69
+ from sklearn.model_selection import train_test_split
70
+
71
+ data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
72
+ for sample in data:
73
+ if len(set(sample['labels'])) > 1:
74
+ sample['is_pos'] = 1
75
+ else:
76
+ sample['is_pos'] = 0
77
+ print("original datapoints: ", len(data))
78
+
79
+
80
+ moredata = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/moredata_dataset_fixed.json'))
81
+ print("moredata datapoints: ", len(moredata))
82
+
83
+
84
+ # %%
85
+ from sklearn.model_selection import GroupKFold
86
+ import random
87
+ random.seed(0)
88
+ kf = GroupKFold(n_splits=5)
89
+ folds = []
90
+ for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
91
+ folds.append([data[i]['document'] for i in valid_index])
92
+
93
+ # %%
94
+ # Define simplified labels
95
+ pii_types = [
96
+ 'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
97
+ 'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
98
+ ]
99
+ all_labels_simplified = sorted(pii_types + ['O'])
100
+ label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
101
+ id2label_simplified = {v: k for k, v in label2id_simplified.items()}
102
+
103
+ # Original target BIO labels
104
+ original_target_bio = sorted([
105
+ 'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
106
+ 'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
107
+ 'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
108
+ 'O'
109
+ ])
110
+
111
+ if 'O' not in original_target_bio:
112
+ original_target_bio_with_o = sorted(list(set(original_target_bio + ['O'])))
113
+ else:
114
+ original_target_bio_with_o = sorted(list(set(original_target_bio)))
115
+
116
+ label2id_original_bio = {l: i for i, l in enumerate(original_target_bio_with_o)}
117
+ id2label_original_bio = {v: k for k, v in label2id_original_bio.items()}
118
+
119
+
120
+ if USE_SIMPLIFIED_LABELS:
121
+ all_labels = all_labels_simplified
122
+ label2id = label2id_simplified
123
+ id2label = id2label_simplified
124
+ print(f"Using SIMPLIFIED labels ({len(all_labels)}): {id2label}")
125
+ else:
126
+ all_labels = original_target_bio_with_o
127
+ label2id = label2id_original_bio
128
+ id2label = id2label_original_bio
129
+ print(f"Using ORIGINAL BIO labels ({len(all_labels)}): {id2label}")
130
+
131
+
132
+ # %% [markdown]
133
+ # # Dataset
134
+
135
+ # %%
136
+ def tokenize(example, tokenizer, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified flag
137
+
138
+ # rebuild text from tokens
139
+ text = []
140
+ char_labels = []
141
+
142
+ for t, l, ws in zip(
143
+ example["tokens"], example["provided_labels"], example["trailing_whitespace"]
144
+ ):
145
+ text.append(t)
146
+ current_label = l
147
+ if use_simplified and l != "O":
148
+ current_label = l[2:] # Strip "B-" or "I-"
149
+
150
+ # Ensure current_label is in the target label set (either simplified or original BIO)
151
+ # If a label from data isn't in the defined 'all_labels', map it to 'O'.
152
+ if current_label not in current_label2id:
153
+ current_label = "O"
154
+
155
+ char_labels.extend([current_label] * len(t))
156
+
157
+ if ws:
158
+ text.append(" ")
159
+ char_labels.append("O") # Whitespace is always "O"
160
+
161
+ tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
162
+
163
+ if len(tokenized['input_ids']) > max_length:
164
+ # "first N/2 + last N/2" truncation strategy
165
+ first_half_len = max_length // 2
166
+ second_half_len = max_length - first_half_len
167
+
168
+ keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
169
+ for key in keys_to_truncate:
170
+ if key in tokenized:
171
+ original_list = tokenized[key]
172
+ # takes the first 'first_half_len' elements and the last 'second_half_len' elements
173
+ tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
174
+
175
+
176
+ char_labels_arr = np.array(char_labels)
177
+
178
+ text_str = "".join(text)
179
+ token_labels = []
180
+
181
+
182
+ current_offset_mapping = tokenized["offset_mapping"]
183
+
184
+ for start_idx, end_idx in current_offset_mapping:
185
+ # Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
186
+
187
+ if start_idx == end_idx:
188
+ token_labels.append(current_label2id["O"]) # Assign 'O' to special/empty tokens
189
+ continue
190
+
191
+ # Ensure indices are within the bounds of char_labels_arr and form a valid span
192
+ # end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
193
+ if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
194
+ token_labels.append(current_label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
195
+ continue
196
+
197
+ char_span_labels = char_labels_arr[start_idx:end_idx]
198
+
199
+
200
+ if not char_span_labels.size:
201
+ token_labels.append(current_label2id["O"])
202
+ continue
203
+
204
+ unique_labels, counts = np.unique(char_span_labels, return_counts=True)
205
+
206
+ pii_labels_present_in_token = []
207
+ pii_counts_present_in_token = []
208
+
209
+ for lbl, count in zip(unique_labels, counts): # lbl is already simplified or original based on earlier logic
210
+ if lbl != "O":
211
+ pii_labels_present_in_token.append(lbl)
212
+ pii_counts_present_in_token.append(count)
213
+
214
+ if pii_labels_present_in_token:
215
+ # If PII labels are present, choose the most frequent PII label
216
+ most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
217
+ most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
218
+ token_labels.append(current_label2id[most_frequent_pii_label])
219
+ else:
220
+ # If only \'O\' is present or no PII labels found in the span
221
+ token_labels.append(current_label2id["O"])
222
+
223
+ length = len(tokenized["input_ids"]) # Use dictionary-style access
224
+
225
+ return {**tokenized, "labels": token_labels, "length": length}
226
+
227
+ # %%
228
+ tokenizer = AutoTokenizer.from_pretrained(backbone)
229
+
230
+ class PIIDataset(Dataset):
231
+ def __init__(self, tokenizer, data, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified
232
+ self.tokenizer = tokenizer
233
+ self.data = copy.deepcopy(data)
234
+ self.current_label2id = current_label2id # Store the active label2id
235
+ self.max_length = max_length
236
+ self.use_simplified = use_simplified
237
+
238
+ for i in tqdm(range(len(self.data))):
239
+ if self.data[i].get('input_ids') is None:
240
+ self.data[i]['provided_labels'] = self.data[i]['labels']
241
+ self.data[i] = tokenize(
242
+ self.data[i],
243
+ self.tokenizer,
244
+ self.current_label2id, # Pass the active label2id
245
+ self.max_length,
246
+ self.use_simplified # Pass the flag
247
+ )
248
+
249
+ def __len__(self):
250
+ return len(self.data)
251
+
252
+ def __getitem__(self, idx):
253
+ return self.data[idx]
254
+
255
+
256
+ def collate_fn(batch):
257
+ input_ids = [torch.tensor(x['input_ids']) for x in batch]
258
+ attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
259
+ labels = [torch.tensor(x['labels']) for x in batch]
260
+
261
+ input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
262
+ attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
263
+ labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
264
+
265
+ return {
266
+ 'input_ids': input_ids_padded,
267
+ 'attention_mask': attention_masks_padded,
268
+ 'labels': labels_padded,
269
+ }
270
+
271
+ # Test with simplified labels
272
+
273
+ ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
274
+ print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
275
+ print(f"Example tokenized labels: {ds[9]['labels']}")
276
+
277
+
278
+ # %% [markdown]
279
+ # # Model
280
+
281
+ # %%
282
+ model = AutoModelForTokenClassification.from_pretrained(
283
+ backbone,
284
+ attention_probs_dropout_prob=attention_probs_dropout_prob,
285
+ hidden_dropout_prob=hidden_dropout_prob,
286
+ num_labels=len(all_labels), # Use length of active labels
287
+ id2label=id2label, # Use active id2label
288
+ label2id=label2id, # Use active label2id
289
+ ignore_mismatched_sizes=True
290
+ )
291
+
292
+ # %% [markdown]
293
+ # # Loss
294
+
295
+ # %%
296
+
297
+ weights = torch.ones(len(all_labels)) * pos_weight
298
+ if 'O' in label2id:
299
+ weights[label2id['O']] = 1
300
+ else:
301
+ print(f"Warning: 'O' label not found in active label2id for weight assignment: {label2id}. Assuming last index for 'O'.")
302
+ weights[-1] = 1
303
+
304
+
305
+ CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
306
+ def criterion(logits, labels):
307
+
308
+
309
+ return CE(logits.view(-1, len(all_labels)), labels.view(-1))
310
+
311
+ # %% [markdown]
312
+ # # Metric
313
+
314
+ # %%
315
+ def compute_metrics(p, current_all_labels_list, th=0.5):
316
+ predictions, labels = p
317
+
318
+ o_label_str = "O"
319
+ o_label_idx = -1
320
+
321
+ for i, lbl_str in enumerate(current_all_labels_list):
322
+ if lbl_str == o_label_str:
323
+ o_label_idx = i
324
+ break
325
+ if o_label_idx == -1:
326
+ print(f"Warning: Could not find 'O' label in {current_all_labels_list} for metric thresholding. Using last index.")
327
+ o_label_idx = len(current_all_labels_list) - 1
328
+
329
+
330
+ true_predictions = []
331
+ true_labels = []
332
+ for tmp, batch_l in zip(predictions, labels):
333
+ batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy()
334
+
335
+ o_column_probs_slice = batch_p_probs[:, :, o_label_idx]
336
+
337
+ condition_mask = o_column_probs_slice < th
338
+
339
+ o_column_probs_slice[condition_mask] = -9999
340
+
341
+ batch_p_final_ids = np.argmax(batch_p_probs, axis=-1)
342
+
343
+ true_predictions.extend([current_all_labels_list[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
344
+ true_labels.extend([current_all_labels_list[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
345
+
346
+ recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
347
+ precision = precision_score([true_labels], [true_predictions], average='micro')
348
+
349
+ beta = 5
350
+ f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
351
+ if precision + recall == 0: # Handle case where P and R are both zero
352
+ f5_score = 0.0
353
+
354
+ results = {
355
+ 'recall': recall,
356
+ 'precision': precision,
357
+ 'f1': f5_score
358
+ }
359
+ return results
360
+
361
+
362
+ # %% [markdown]
363
+ # # Train & Valid
364
+
365
+ # %%
366
+ def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
367
+ model.train()
368
+ running_loss = []
369
+ bar = tqdm(loader, total=len(loader))
370
+ for batch in bar:
371
+ optimizer.zero_grad()
372
+
373
+ input_ids = batch['input_ids'].to(device)
374
+ attention_mask = batch['attention_mask'].to(device)
375
+ labels = batch['labels'].to(device)
376
+
377
+ if scaler is not None:
378
+ with torch.cuda.amp.autocast():
379
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
380
+ loss = criterion(outputs['logits'], labels)
381
+ scaler.scale(loss).backward()
382
+ scaler.step(optimizer)
383
+ scaler.update()
384
+ else:
385
+ outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
386
+ loss = criterion(outputs['logits'], labels)
387
+ loss.backward()
388
+ optimizer.step()
389
+
390
+ scheduler.step()
391
+ running_loss.append(loss.item())
392
+ bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
393
+
394
+ return np.mean(running_loss)
395
+
396
+
397
+ def valid_one_epoch(model, loader):
398
+ model.eval()
399
+ running_loss = []
400
+ bar = tqdm(loader, total=len(loader))
401
+ predictions_logits = []
402
+ true_labels_list = []
403
+
404
+ with torch.no_grad():
405
+ for batch in bar:
406
+ input_ids = batch['input_ids'].to(device)
407
+ attention_mask = batch['attention_mask'].to(device)
408
+ labels_batch = batch['labels'].to(device)
409
+
410
+ outputs = model(input_ids, attention_mask=attention_mask)
411
+ loss = criterion(outputs.logits, labels_batch)
412
+ running_loss.append(loss.item())
413
+
414
+ predictions_logits.append(outputs.logits.cpu().numpy())
415
+ true_labels_list.append(labels_batch.cpu().numpy())
416
+
417
+ bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
418
+
419
+ # Pass the active all_labels (list of strings) to compute_metrics
420
+ metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels)
421
+ return np.mean(running_loss), metrics_results
422
+
423
+ # %% [markdown]
424
+ # # Scheduler
425
+
426
+ # %%
427
+ rcParams['figure.figsize'] = 20, 2
428
+
429
+ def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
430
+ return get_cosine_schedule_with_warmup(
431
+ optimizer,
432
+ int(num_training_steps * warmup_steps_ratio),
433
+ num_training_steps
434
+ )
435
+
436
+ opt = AdamW(model.parameters(), lr=lr)
437
+ sch = get_scheduler(opt, 100, 0.1)
438
+
439
+ lrs = []
440
+ for epoch in range(1, 100+1):
441
+ sch.step()
442
+ lrs.append(opt.param_groups[0]["lr"])
443
+
444
+ plt.plot(range(len(lrs)), lrs)
445
+
446
+ # %% [markdown]
447
+ # # Run
448
+
449
+ # %%
450
+ def run(fold,model_to_train):
451
+
452
+ log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
453
+
454
+ model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
455
+ model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
456
+
457
+
458
+ os.makedirs(model_save_dir_best, exist_ok=True)
459
+ os.makedirs(model_save_dir_last, exist_ok=True)
460
+
461
+ optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
462
+
463
+
464
+ valid_doc_ids = folds[fold]
465
+ train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
466
+
467
+ valid_data = [x for x in data if x['document'] in valid_doc_ids]
468
+
469
+ dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
470
+ dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
471
+ print(len(dataset_train), len(dataset_valid))
472
+
473
+ loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn)
474
+ loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
475
+
476
+ current_model = model_to_train.to(device)
477
+
478
+ optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
479
+ scaler = torch.cuda.amp.GradScaler() if use_amp else None
480
+ from_epoch = 0
481
+ metric_best = 0
482
+
483
+ scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
484
+
485
+ for epoch in range(1, n_epochs+1):
486
+ print(time.ctime(), 'Epoch:', epoch)
487
+
488
+ train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
489
+ valid_loss, result = valid_one_epoch(current_model, loader_valid)
490
+ metric = result["f1"] # F5 score on simplified labels
491
+
492
+ content = time.ctime() + ' ' + \
493
+ f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
494
+ f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
495
+ f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
496
+ print(content)
497
+ with open(log_file, 'a') as appender:
498
+ appender.write(content + '\n')
499
+
500
+ if metric > metric_best:
501
+ print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
502
+ if not DEBUG:
503
+ current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
504
+ tokenizer.save_pretrained(model_save_dir_best)
505
+ metric_best = metric
506
+
507
+ if not DEBUG:
508
+ current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
509
+ tokenizer.save_pretrained(model_save_dir_last)
510
+ torch.save(
511
+ {
512
+ 'epoch': epoch,
513
+ 'optimizer_state_dict': optimizer.state_dict(),
514
+ 'scheduler_state_dict': scheduler.state_dict(),
515
+ 'scaler_state_dict': scaler.state_dict() if scaler else None,
516
+ 'metric_best': metric_best,
517
+ },
518
+ optimizer_etc_file_last
519
+ )
520
+
521
+ del current_model
522
+ torch.cuda.empty_cache()
523
+ gc.collect()
524
+
525
+ # %%
526
+ run(0, model)
527
+
528
+ # %%
529
+
530
+
531
+
config.json ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_name_or_path": "microsoft/deberta-v3-large",
3
+ "attention_probs_dropout_prob": 0.1,
4
+ "hidden_act": "gelu",
5
+ "hidden_dropout_prob": 0.1,
6
+ "hidden_size": 1024,
7
+ "id2label": {
8
+ "0": "LABEL_0",
9
+ "1": "LABEL_1",
10
+ "2": "LABEL_2",
11
+ "3": "LABEL_3",
12
+ "4": "LABEL_4",
13
+ "5": "LABEL_5",
14
+ "6": "LABEL_6",
15
+ "7": "LABEL_7",
16
+ "8": "LABEL_8",
17
+ "9": "LABEL_9",
18
+ "10": "LABEL_10",
19
+ "11": "LABEL_11",
20
+ "12": "LABEL_12"
21
+ },
22
+ "initializer_range": 0.02,
23
+ "intermediate_size": 4096,
24
+ "label2id": {
25
+ "LABEL_0": 0,
26
+ "LABEL_1": 1,
27
+ "LABEL_10": 10,
28
+ "LABEL_11": 11,
29
+ "LABEL_12": 12,
30
+ "LABEL_2": 2,
31
+ "LABEL_3": 3,
32
+ "LABEL_4": 4,
33
+ "LABEL_5": 5,
34
+ "LABEL_6": 6,
35
+ "LABEL_7": 7,
36
+ "LABEL_8": 8,
37
+ "LABEL_9": 9
38
+ },
39
+ "layer_norm_eps": 1e-07,
40
+ "max_position_embeddings": 512,
41
+ "max_relative_positions": -1,
42
+ "model_type": "deberta-v2",
43
+ "norm_rel_ebd": "layer_norm",
44
+ "num_attention_heads": 16,
45
+ "num_hidden_layers": 24,
46
+ "pad_token_id": 0,
47
+ "pooler_dropout": 0,
48
+ "pooler_hidden_act": "gelu",
49
+ "pooler_hidden_size": 1024,
50
+ "pos_att_type": [
51
+ "p2c",
52
+ "c2p"
53
+ ],
54
+ "position_biased_input": false,
55
+ "position_buckets": 256,
56
+ "relative_attention": true,
57
+ "share_att_key": true,
58
+ "target_modules": null,
59
+ "transformers_version": "4.38.2",
60
+ "type_vocab_size": 0,
61
+ "vocab_size": 128100
62
+ }
deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49842d0c1521ba11d9d9d0c86a4ecb5c1b519f72b7a15a6a173be165b72a706f
3
+ size 1736286050
deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_last.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c0b7f029d3421c9078ef5f341ce237ff8bc3395beeb625c484343a69204ec13
3
+ size 5208909490
special_tokens_map.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "[CLS]",
3
+ "cls_token": "[CLS]",
4
+ "eos_token": "[SEP]",
5
+ "mask_token": "[MASK]",
6
+ "pad_token": "[PAD]",
7
+ "sep_token": "[SEP]",
8
+ "unk_token": {
9
+ "content": "[UNK]",
10
+ "lstrip": false,
11
+ "normalized": true,
12
+ "rstrip": false,
13
+ "single_word": false
14
+ }
15
+ }
spm.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c679fbf93643d19aab7ee10c0b99e460bdbc02fedf34b92b05af343b4af586fd
3
+ size 2464616
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "1": {
12
+ "content": "[CLS]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "[SEP]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "[UNK]",
29
+ "lstrip": false,
30
+ "normalized": true,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "128000": {
36
+ "content": "[MASK]",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ }
43
+ },
44
+ "bos_token": "[CLS]",
45
+ "clean_up_tokenization_spaces": true,
46
+ "cls_token": "[CLS]",
47
+ "do_lower_case": false,
48
+ "eos_token": "[SEP]",
49
+ "mask_token": "[MASK]",
50
+ "model_max_length": 1000000000000000019884624838656,
51
+ "pad_token": "[PAD]",
52
+ "sep_token": "[SEP]",
53
+ "sp_model_kwargs": {},
54
+ "split_by_punct": false,
55
+ "tokenizer_class": "DebertaV2Tokenizer",
56
+ "unk_token": "[UNK]",
57
+ "vocab_type": "spm"
58
+ }