somukandula commited on
Commit
bba0068
·
verified ·
1 Parent(s): e502f68

Upload phase2/retrain_with_augmentation.py

Browse files
Files changed (1) hide show
  1. phase2/retrain_with_augmentation.py +208 -0
phase2/retrain_with_augmentation.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Maskara Phase 2 Retraining with Targeted Augmentation
3
+ =======================================================
4
+ 1. Downloads the current dataset from somukandula/maskara-indian-pii-200k.
5
+ 2. Generates targeted synthetic examples for under-performing entities.
6
+ 3. Merges them into the train split.
7
+ 4. Retrains from bert-base-uncased (or optionally from current Maskara weights).
8
+ 5. Evaluates and pushes the updated model to somukandula/maskara.
9
+
10
+ Run on Modal GPU:
11
+ pip install torch transformers datasets evaluate trackio accelerate
12
+ python phase2/retrain_with_augmentation.py
13
+ """
14
+ import os
15
+ import json
16
+ import warnings
17
+
18
+ import numpy as np
19
+ import evaluate
20
+ from datasets import load_dataset, Dataset, concatenate_datasets
21
+ from transformers import (
22
+ AutoTokenizer,
23
+ AutoModelForTokenClassification,
24
+ DataCollatorForTokenClassification,
25
+ TrainingArguments,
26
+ Trainer,
27
+ EarlyStoppingCallback,
28
+ set_seed,
29
+ )
30
+
31
+ warnings.filterwarnings("ignore", message=r".*unexpected keys in the checkpoint model loaded.*")
32
+ warnings.filterwarnings("ignore", message=r".*missing keys in the checkpoint model loaded.*")
33
+
34
+ MODEL_NAME = "google-bert/bert-base-uncased" # change to "somukandula/maskara" for continued fine-tuning
35
+ DATASET_NAME = "somukandula/maskara-indian-pii-200k"
36
+ OUTPUT_DIR = "/app/maskara-phase2-retrain-output"
37
+ HUB_MODEL_ID = "somukandula/maskara"
38
+ TRACKIO_PROJECT = "maskara-phase2"
39
+
40
+ SUPPORTED_ENTITIES = [
41
+ "ADDRESS", "API_KEY", "CREDIT_CARD", "DATE_OF_BIRTH", "DRIVER_LICENSE",
42
+ "EMAIL", "IP_ADDRESS", "PASSWORD", "PERSON_NAME", "PHONE", "SSN",
43
+ "USERNAME", "AADHAAR", "PAN_CARD", "PASSPORT", "UPI_ID", "VEHICLE_REG",
44
+ ]
45
+
46
+ label_list = ["O"] + [f"B-{e}" for e in SUPPORTED_ENTITIES] + [f"I-{e}" for e in SUPPORTED_ENTITIES]
47
+ label2id = {l: i for i, l in enumerate(label_list)}
48
+ id2label = {i: l for l, i in label2id.items()}
49
+
50
+ set_seed(2027)
51
+
52
+ # Import augmentation generator
53
+ import importlib.util
54
+ spec = importlib.util.spec_from_file_location("targeted_augmentation", "phase2/targeted_augmentation.py")
55
+ aug = importlib.util.module_from_spec(spec)
56
+ spec.loader.exec_module(aug)
57
+
58
+
59
+ def tokenize_and_align_labels(examples, tokenizer):
60
+ tokenized = tokenizer(
61
+ examples["text"],
62
+ truncation=True,
63
+ max_length=256,
64
+ return_offsets_mapping=True,
65
+ )
66
+ all_labels = []
67
+ for i in range(len(examples["text"])):
68
+ offsets = tokenized.offset_mapping[i]
69
+ labels = ["O"] * len(offsets)
70
+ for ent in examples["entities"][i]:
71
+ es, ee, el = ent["start"], ent["end"], ent["label"]
72
+ if el not in SUPPORTED_ENTITIES:
73
+ continue
74
+ first = True
75
+ for j, (ts, te) in enumerate(offsets):
76
+ if ts == te == 0:
77
+ continue
78
+ if te <= es or ts >= ee:
79
+ continue
80
+ labels[j] = f"B-{el}" if first else f"I-{el}"
81
+ first = False
82
+ all_labels.append([label2id.get(l, 0) for l in labels])
83
+ tokenized["labels"] = all_labels
84
+ tokenized.pop("offset_mapping", None)
85
+ return tokenized
86
+
87
+
88
+ def main():
89
+ print("Loading tokenizer and dataset...")
90
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)
91
+ dataset = load_dataset(DATASET_NAME)
92
+
93
+ print("Generating targeted augmentation...")
94
+ rng = __import__("random").Random(2027)
95
+ aug_examples = aug.generate_targeted_examples(rng, n_per_entity=2000)
96
+ aug_dataset = Dataset.from_list(aug_examples)
97
+
98
+ print(f"Original train size: {len(dataset['train'])}")
99
+ train = concatenate_datasets([dataset["train"], aug_dataset])
100
+ print(f"Augmented train size: {len(train)}")
101
+
102
+ tokenized = {}
103
+ for split in ["train", "template_disjoint_eval", "real_world_eval"]:
104
+ src = train if split == "train" else dataset[split]
105
+ tokenized[split] = src.map(
106
+ lambda ex: tokenize_and_align_labels(ex, tokenizer),
107
+ batched=True,
108
+ remove_columns=src.column_names,
109
+ num_proc=4,
110
+ desc=f"tokenize {split}",
111
+ )
112
+
113
+ seqeval = evaluate.load("seqeval")
114
+
115
+ def compute_metrics(p):
116
+ predictions, labels = p
117
+ predictions = np.argmax(predictions, axis=2)
118
+ true_predictions = [
119
+ [label_list[p] for p, lab in zip(prediction, label) if lab != -100]
120
+ for prediction, label in zip(predictions, labels)
121
+ ]
122
+ true_labels = [
123
+ [label_list[lab] for p, lab in zip(prediction, label) if lab != -100]
124
+ for prediction, label in zip(predictions, labels)
125
+ ]
126
+ results = seqeval.compute(predictions=true_predictions, references=true_labels, zero_division=0)
127
+ out = {
128
+ "precision": results["overall_precision"],
129
+ "recall": results["overall_recall"],
130
+ "f1": results["overall_f1"],
131
+ "accuracy": results["overall_accuracy"],
132
+ }
133
+ for key, val in results.items():
134
+ if isinstance(val, dict):
135
+ out[f"{key}_f1"] = val.get("f1", 0.0)
136
+ return out
137
+
138
+ print("Loading model...")
139
+ model = AutoModelForTokenClassification.from_pretrained(
140
+ MODEL_NAME,
141
+ num_labels=len(label_list),
142
+ id2label=id2label,
143
+ label2id=label2id,
144
+ ignore_mismatched_sizes=True,
145
+ )
146
+
147
+ training_args = TrainingArguments(
148
+ output_dir=OUTPUT_DIR,
149
+ overwrite_output_dir=True,
150
+ num_train_epochs=5,
151
+ per_device_train_batch_size=32,
152
+ per_device_eval_batch_size=64,
153
+ learning_rate=3e-5,
154
+ weight_decay=0.01,
155
+ warmup_ratio=0.1,
156
+ lr_scheduler_type="linear",
157
+ eval_strategy="epoch",
158
+ save_strategy="epoch",
159
+ logging_strategy="steps",
160
+ logging_steps=50,
161
+ logging_first_step=True,
162
+ disable_tqdm=True,
163
+ load_best_model_at_end=True,
164
+ metric_for_best_model="f1",
165
+ greater_is_better=True,
166
+ bf16=True,
167
+ optim="adamw_torch",
168
+ report_to="trackio",
169
+ run_name="maskara-phase2-retrain-targeted",
170
+ project=TRACKIO_PROJECT,
171
+ push_to_hub=True,
172
+ hub_model_id=HUB_MODEL_ID,
173
+ hub_strategy="end",
174
+ seed=2027,
175
+ )
176
+
177
+ trainer = Trainer(
178
+ model=model,
179
+ args=training_args,
180
+ train_dataset=tokenized["train"],
181
+ eval_dataset=tokenized["template_disjoint_eval"],
182
+ processing_class=tokenizer,
183
+ data_collator=DataCollatorForTokenClassification(tokenizer, pad_to_multiple_of=8),
184
+ compute_metrics=compute_metrics,
185
+ callbacks=[EarlyStoppingCallback(early_stopping_patience=2)],
186
+ )
187
+
188
+ print("Training...")
189
+ trainer.train()
190
+
191
+ td_metrics = trainer.evaluate(tokenized["template_disjoint_eval"])
192
+ rw_metrics = trainer.evaluate(tokenized["real_world_eval"])
193
+
194
+ metrics = {
195
+ "template_disjoint_eval": td_metrics,
196
+ "real_world_eval": rw_metrics,
197
+ "label_list": label_list,
198
+ "num_parameters": sum(p.numel() for p in model.parameters()),
199
+ }
200
+ with open(os.path.join(OUTPUT_DIR, "retrain_metrics.json"), "w") as f:
201
+ json.dump(metrics, f, indent=2, default=float)
202
+
203
+ trainer.push_to_hub(commit_message="Maskara Phase 2 retrain with targeted augmentation")
204
+ print("Done. Pushed to", HUB_MODEL_ID)
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()