zeronamoni commited on
Commit
7d9484b
·
verified ·
1 Parent(s): 6ed4ac0

Upload project/main.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. project/main.py +184 -0
project/main.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """End-to-end CLI for reproducible TMFT experiments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import gc
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+
11
+ os.environ.setdefault("USE_TF", "0")
12
+ os.environ.setdefault("TRANSFORMERS_NO_TF", "1")
13
+
14
+ import pandas as pd
15
+ import torch
16
+ from datasets import load_from_disk
17
+
18
+ from src.data_prep import prepare_experiment_data
19
+ from src.evaluate_mia import evaluate_mia_auc
20
+ from src.evaluate_pii import evaluate_pii, load_pii_eval_set
21
+ from src.evaluate_ppl import evaluate_perplexity
22
+ from src.plot_results import plot_results
23
+ from src.train import (
24
+ METHODS,
25
+ load_config,
26
+ load_tokenizer,
27
+ load_trained_model,
28
+ train_model,
29
+ upload_to_huggingface,
30
+ )
31
+
32
+
33
+ def parse_args():
34
+ parser = argparse.ArgumentParser(description="TMFT experiment orchestration")
35
+ parser.add_argument("--mode", choices=["prepare", "train", "eval", "plot", "upload", "all"], required=True)
36
+ parser.add_argument("--method", choices=[*METHODS, "all"], default="all")
37
+ parser.add_argument("--config", default="configs/config.yaml")
38
+ parser.add_argument("--force_prepare", action="store_true")
39
+ parser.add_argument("--model_dir", default=None, help="Override model directory for single-method eval/upload")
40
+ parser.add_argument("--hf_repo_id", default=None)
41
+ parser.add_argument("--public", action="store_true")
42
+ return parser.parse_args()
43
+
44
+
45
+ def selected_methods(method: str) -> list[str]:
46
+ return list(METHODS) if method == "all" else [method]
47
+
48
+
49
+ def ensure_prepared(config: dict, force: bool = False):
50
+ splits, eval_path = prepare_experiment_data(config, force=force)
51
+ config["text_column"] = "text"
52
+ print(
53
+ json.dumps(
54
+ {"train": len(splits["train"]), "validation": len(splits["validation"]), "test": len(splits["test"]),
55
+ "pii_eval_path": str(eval_path)},
56
+ indent=2,
57
+ )
58
+ )
59
+ return splits, eval_path
60
+
61
+
62
+ def run_train(config: dict, method: str, splits) -> dict[str, str]:
63
+ outputs: dict[str, str] = {}
64
+ for current_method in selected_methods(method):
65
+ print(f"\n===== TRAIN: {current_method} =====")
66
+ _, _, output_dir = train_model(
67
+ config,
68
+ method=current_method,
69
+ train_dataset=splits["train"],
70
+ eval_dataset=splits["validation"],
71
+ )
72
+ outputs[current_method] = str(output_dir)
73
+ return outputs
74
+
75
+
76
+ def _model_directory(config: dict, method: str, override: str | None) -> Path:
77
+ return Path(override) if override else Path(config.get("output_dir", "results")) / method
78
+
79
+
80
+ def run_eval(config: dict, method: str, splits, eval_path: Path, model_dir: str | None = None) -> pd.DataFrame:
81
+ eval_set = load_pii_eval_set(eval_path)
82
+ rows: list[dict[str, object]] = []
83
+ for current_method in selected_methods(method):
84
+ current_dir = _model_directory(config, current_method, model_dir if method != "all" else None)
85
+ if not current_dir.exists():
86
+ raise FileNotFoundError(f"Missing trained model for {current_method}: {current_dir}")
87
+ print(f"\n===== EVAL: {current_method} =====")
88
+ tokenizer = load_tokenizer(str(current_dir))
89
+ model = load_trained_model(current_dir)
90
+ if torch.cuda.is_available():
91
+ model = model.cuda()
92
+
93
+ pii = evaluate_pii(
94
+ model,
95
+ tokenizer,
96
+ eval_set,
97
+ max_new_tokens=int(config.get("eval_max_new_tokens", 50)),
98
+ )
99
+ ppl = evaluate_perplexity(
100
+ model,
101
+ tokenizer,
102
+ splits["validation"],
103
+ max_seq_len=int(config.get("max_seq_len", 512)),
104
+ batch_size=int(config.get("eval_batch_size", 4)),
105
+ )
106
+ mia = evaluate_mia_auc(
107
+ model,
108
+ tokenizer,
109
+ splits["train"],
110
+ splits["test"],
111
+ max_samples=int(config.get("mia_eval_samples", 250)),
112
+ max_seq_len=int(config.get("max_seq_len", 512)),
113
+ batch_size=int(config.get("eval_batch_size", 4)),
114
+ min_k=int(config.get("min_k_percent", 20)),
115
+ )
116
+ metadata_path = current_dir / "training_metadata.json"
117
+ metadata = json.loads(metadata_path.read_text(encoding="utf-8")) if metadata_path.exists() else {}
118
+ rows.append(
119
+ {
120
+ "method": current_method,
121
+ "ter": pii["ter"],
122
+ "ser": pii["ser"],
123
+ "ppl": ppl["ppl"],
124
+ "loss_mia_auc": mia["loss_mia_auc"],
125
+ "min_k_mia_auc": mia["min_k_mia_auc"],
126
+ "masked_token_ratio": metadata.get("masked_token_ratio", 0.0),
127
+ "skipped_samples": metadata.get("skipped_samples", 0),
128
+ "pii_eval_samples": pii["total_samples"],
129
+ "mia_samples_per_class": mia["mia_samples_per_class"],
130
+ }
131
+ )
132
+ del model
133
+ gc.collect()
134
+ if torch.cuda.is_available():
135
+ torch.cuda.empty_cache()
136
+
137
+ frame = pd.DataFrame(rows)
138
+ if "baseline" in set(frame["method"]):
139
+ baseline_ppl = float(frame.loc[frame["method"] == "baseline", "ppl"].iloc[0])
140
+ frame["mdp"] = frame["ppl"] - baseline_ppl
141
+ else:
142
+ frame["mdp"] = float("nan")
143
+ tables_dir = Path(config.get("results_table_dir", "results/tables"))
144
+ tables_dir.mkdir(parents=True, exist_ok=True)
145
+ output_path = tables_dir / "main_results.csv"
146
+ frame.to_csv(output_path, index=False)
147
+ print(f"Saved results: {output_path}")
148
+ return frame
149
+
150
+
151
+ def main():
152
+ args = parse_args()
153
+ config = load_config(args.config)
154
+
155
+ if args.mode == "prepare":
156
+ ensure_prepared(config, force=args.force_prepare)
157
+ return
158
+
159
+ if args.mode == "plot":
160
+ csv_path = Path(config.get("results_table_dir", "results/tables")) / "main_results.csv"
161
+ print([str(path) for path in plot_results(csv_path)])
162
+ return
163
+
164
+ if args.mode == "upload":
165
+ if not args.hf_repo_id or args.method == "all":
166
+ raise ValueError("Upload requires --hf_repo_id and one specific --method")
167
+ directory = _model_directory(config, args.method, args.model_dir)
168
+ upload_to_huggingface(directory, args.hf_repo_id, private=not args.public)
169
+ print(json.dumps({"uploaded": args.hf_repo_id, "model_dir": str(directory)}, indent=2))
170
+ return
171
+
172
+ splits, eval_path = ensure_prepared(config, force=args.force_prepare)
173
+ if args.mode in {"train", "all"}:
174
+ print(json.dumps({"trained": run_train(config, args.method, splits)}, indent=2))
175
+ if args.mode in {"eval", "all"}:
176
+ frame = run_eval(config, args.method, splits, eval_path, args.model_dir)
177
+ print(frame.to_string(index=False))
178
+ if args.mode == "all":
179
+ csv_path = Path(config.get("results_table_dir", "results/tables")) / "main_results.csv"
180
+ print([str(path) for path in plot_results(csv_path)])
181
+
182
+
183
+ if __name__ == "__main__":
184
+ main()