patientphex-task1 / scripts /train_predict_candidate_verifier.py
SocrAIte's picture
Upload folder using huggingface_hub
e7543dc verified
Raw History Blame Contribute Delete
5.54 kB
from __future__ import annotations
import argparse
import json
import math
import os
from pathlib import Path
import random
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import numpy as np
import torch
from torch.optim import AdamW
from torch.utils.data import DataLoader
from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_linear_schedule_with_warmup
from train_candidate_verifier import VerifierDataset, build_examples, score
def load_jsonl(path: Path) -> list[dict]:
return [json.loads(line) for line in path.open(encoding="utf-8") if line.strip()]
def main() -> None:
parser = argparse.ArgumentParser(description="Train a full-data phenotype candidate verifier and score deployment candidates.")
parser.add_argument("--train-data", type=Path, required=True)
parser.add_argument("--train-candidates", type=Path, required=True)
parser.add_argument("--predict-data", type=Path, required=True)
parser.add_argument("--predict-candidates", type=Path, required=True)
parser.add_argument("--model", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--epochs", type=int, default=3)
parser.add_argument("--batch-size", type=int, default=16)
parser.add_argument("--learning-rate", type=float, default=2e-5)
parser.add_argument("--max-length", type=int, default=256)
parser.add_argument("--seed", type=int, default=2026)
parser.add_argument("--device", default="cuda")
args = parser.parse_args()
os.environ.setdefault("HF_HUB_OFFLINE", "1")
os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
random.seed(args.seed)
np.random.seed(args.seed)
torch.manual_seed(args.seed)
train_documents = load_jsonl(args.train_data)
predict_documents = load_jsonl(args.predict_data)
train_candidates = json.loads(args.train_candidates.read_text(encoding="utf-8"))
predict_candidates = json.loads(args.predict_candidates.read_text(encoding="utf-8"))
train_examples = build_examples(train_documents, train_candidates)
predict_examples = build_examples(predict_documents, predict_candidates)
tokenizer = AutoTokenizer.from_pretrained(args.model, local_files_only=True, use_fast=True)
train_loader = DataLoader(
VerifierDataset(train_examples, tokenizer, args.max_length), batch_size=args.batch_size,
shuffle=True, generator=torch.Generator().manual_seed(args.seed),
num_workers=2, pin_memory=True,
)
predict_loader = DataLoader(
VerifierDataset(predict_examples, tokenizer, args.max_length), batch_size=args.batch_size,
shuffle=False, num_workers=2, pin_memory=True,
)
model = AutoModelForSequenceClassification.from_pretrained(
args.model, local_files_only=True, num_labels=2, ignore_mismatched_sizes=True
).to(args.device)
positives = sum(example.label for example in train_examples)
class_weights = torch.tensor(
[1.0, (len(train_examples) - positives) / max(1, positives)], device=args.device
)
optimizer = AdamW(model.parameters(), lr=args.learning_rate, weight_decay=0.01)
updates = len(train_loader) * args.epochs
scheduler = get_linear_schedule_with_warmup(optimizer, max(1, updates // 10), updates)
history: list[dict[str, float | int]] = []
for epoch in range(1, args.epochs + 1):
model.train()
total_loss = 0.0
for batch in train_loader:
batch.pop("index")
labels = batch.pop("labels").to(args.device)
inputs = {key: value.to(args.device, non_blocking=True) for key, value in batch.items()}
with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=args.device.startswith("cuda")):
logits = model(**inputs).logits
loss = torch.nn.functional.cross_entropy(logits.float(), labels, weight=class_weights)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
optimizer.zero_grad(set_to_none=True)
total_loss += float(loss.item())
report = {"epoch": epoch, "train_loss": total_loss / max(1, len(train_loader))}
history.append(report)
print(json.dumps(report), flush=True)
scores = score(model, predict_loader, args.device, len(predict_examples))
predictions = [
{
"pmc_id": example.pmc_id, "start": example.start, "end": example.end,
"text": example.text, "score": value, **example.metadata,
}
for example, value in zip(predict_examples, scores, strict=True)
]
result = {
"configuration": vars(args) | {
"train_data": str(args.train_data), "train_candidates": str(args.train_candidates),
"predict_data": str(args.predict_data), "predict_candidates": str(args.predict_candidates),
"model": str(args.model), "output": str(args.output),
},
"train_examples": len(train_examples), "train_positives": positives,
"predict_examples": len(predict_examples), "history": history,
"predictions": predictions,
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
print(json.dumps({"train_examples": len(train_examples), "predict_examples": len(predict_examples), "output": str(args.output)}))
if __name__ == "__main__":
main()