peter2000 commited on
Commit
dd604fc
·
verified ·
1 Parent(s): 293c8a8

Add training script: SetFit one-vs-rest multi-label classifier on GIZ vulnerability data

Browse files
Files changed (1) hide show
  1. train_setfit.py +134 -0
train_setfit.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ.setdefault("USE_TF", "0")
3
+ os.environ.setdefault("USE_TORCH", "1")
4
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
5
+
6
+ import json
7
+ import time
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ import trackio
12
+
13
+ def _trackio_safe(fn):
14
+ def wrapper(*a, **k):
15
+ try:
16
+ return fn(*a, **k)
17
+ except Exception as e:
18
+ print(f"trackio.{fn.__name__} failed (non-fatal): {e}")
19
+ return wrapper
20
+ trackio.log = _trackio_safe(trackio.log)
21
+ trackio.init = _trackio_safe(trackio.init)
22
+ trackio.finish = _trackio_safe(trackio.finish)
23
+
24
+ from datasets import Dataset
25
+ from huggingface_hub import HfApi
26
+ from sklearn.metrics import f1_score
27
+ from sklearn.model_selection import train_test_split
28
+ from setfit import SetFitModel, TrainingArguments, Trainer
29
+
30
+ SMOKE = os.environ.get("SMOKE", "0") == "1"
31
+ PARQUET_URL = "https://huggingface.co/datasets/GIZ/vulnerability_training_data_full/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet"
32
+ LABELS = [
33
+ "Agricultural communities", "Coastal communities", "Ethnic, racial or other minorities",
34
+ "Fishery communities", "Informal sector workers", "Members of indigenous and local communities",
35
+ "Migrants and displaced persons", "Older persons", "Other", "Persons living in poverty",
36
+ "Persons with disabilities", "Persons with pre-existing health conditions",
37
+ "Residents of drought-prone regions", "Rural populations", "Sexual minorities (LGBTQI+)",
38
+ "Urban populations", "Women and other genders",
39
+ ]
40
+ REPO_ID = "peter2000/setfit-vulnerability-groups"
41
+
42
+ def load_data():
43
+ df = pd.read_parquet(PARQUET_URL)
44
+ assert len(df) == 475, f"expected 475 rows, got {len(df)}"
45
+ Y = df[LABELS].values.astype(np.int64)
46
+ nlab = Y.sum(1)
47
+ idx_tr, idx_te = train_test_split(
48
+ np.arange(len(df)), test_size=0.2, random_state=42, stratify=np.minimum(nlab, 3)
49
+ )
50
+ return df, Y, np.asarray(idx_tr), np.asarray(idx_te)
51
+
52
+ def ece(conf, correct, n_bins=15):
53
+ conf = np.asarray(conf, dtype=np.float64)
54
+ corr = np.asarray(correct, dtype=np.float64)
55
+ bins = np.linspace(0.0, 1.0, n_bins + 1)
56
+ e = 0.0
57
+ for lo, hi in zip(bins[:-1], bins[1:]):
58
+ m = (conf > lo) & (conf <= hi)
59
+ if m.sum() > 0:
60
+ e += m.mean() * abs(corr[m].mean() - conf[m].mean())
61
+ return float(e)
62
+
63
+ def evaluate(Y_true, P_pred, threshold=0.5):
64
+ pred = (P_pred >= threshold).astype(int)
65
+ per_label = f1_score(Y_true, pred, average=None, zero_division=0)
66
+ macro = float(f1_score(Y_true, pred, average="macro", zero_division=0))
67
+ micro = float(f1_score(Y_true, pred, average="micro", zero_division=0))
68
+ conf = np.where(pred == 1, P_pred, 1.0 - P_pred)
69
+ corr = (pred == Y_true).astype(np.float64)
70
+ return {
71
+ "macro_f1": macro,
72
+ "micro_f1": micro,
73
+ "ece": ece(conf, corr),
74
+ "subset_accuracy": float(((pred == Y_true).all(axis=1)).mean()),
75
+ "per_label_f1": {LABELS[i]: round(float(per_label[i]), 4) for i in range(len(LABELS))},
76
+ }
77
+
78
+ def main():
79
+ df, Y, idx_tr, idx_te = load_data()
80
+ texts = df["text"].tolist()
81
+ X_tr = [texts[i] for i in idx_tr]
82
+ X_te = [texts[i] for i in idx_te]
83
+ Y_tr, Y_te = Y[idx_tr], Y[idx_te]
84
+ print(f"train={len(X_tr)} test={len(X_te)} labels={len(LABELS)}")
85
+
86
+ if SMOKE:
87
+ X_tr, Y_tr = X_tr[:40], Y_tr[:40]
88
+ X_te, Y_te = X_te[:10], Y_te[:10]
89
+
90
+ model = SetFitModel.from_pretrained(
91
+ "sentence-transformers/paraphrase-mpnet-base-v2",
92
+ multi_target_strategy="one-vs-rest",
93
+ labels=LABELS,
94
+ )
95
+ model.model_body.max_seq_length = 256
96
+
97
+ train_ds = Dataset.from_dict({
98
+ "text": X_tr,
99
+ "label": [list(map(int, y)) for y in Y_tr],
100
+ })
101
+
102
+ args = TrainingArguments(
103
+ batch_size=16,
104
+ num_epochs=1,
105
+ num_iterations=20 if not SMOKE else 2,
106
+ seed=42,
107
+ )
108
+ trainer = Trainer(model=model, args=args, train_dataset=train_ds)
109
+ trainer.train()
110
+
111
+ P_te = np.asarray(model.predict_proba(X_te))
112
+ metrics = evaluate(Y_te, P_te)
113
+ print(json.dumps(metrics, indent=2))
114
+
115
+ trackio.init(project="vulnerability-multilabel-classifier", space_id="peter2000/vulnerability-multilabel-classifier-trackio")
116
+ trackio.log({"setfit_macro_f1": metrics["macro_f1"], "setfit_micro_f1": metrics["micro_f1"], "setfit_ece": metrics["ece"]}, step=1)
117
+ trackio.log({f"setfit_f1/{k}": v for k, v in metrics["per_label_f1"].items()}, step=1)
118
+
119
+ trainer.push_to_hub(repo_id=REPO_ID)
120
+ api = HfApi(token=os.environ.get("HF_TOKEN"))
121
+ api.upload_file(
122
+ path_or_fileobj=json.dumps(metrics, indent=2).encode(),
123
+ path_in_repo="metrics.json",
124
+ repo_id=REPO_ID,
125
+ repo_type="model",
126
+ commit_message="Add evaluation metrics (macro-F1, micro-F1, per-label F1, ECE)",
127
+ )
128
+ trackio.finish()
129
+ print("DONE")
130
+
131
+ if __name__ == "__main__":
132
+ t0 = time.time()
133
+ main()
134
+ print(f"elapsed {time.time()-t0:.0f}s")