Upload mine_hard_negs.py with huggingface_hub
Browse files- mine_hard_negs.py +82 -0
mine_hard_negs.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Mine hard negatives from a V9+ eval dump for next-iteration training.
|
| 2 |
+
|
| 3 |
+
For each failure (intent_em or slots_em wrong on canonical 253):
|
| 4 |
+
- Output the row with corrected gold (from canonical scenario)
|
| 5 |
+
- Tag with failure type: wrong_intent | missing_slot | extra_slot | wrong_segment_count
|
| 6 |
+
- Optionally synthesize a near-duplicate by light text mutation
|
| 7 |
+
|
| 8 |
+
Output: poc/llm-finetune/training/data_canonical_v2/hard_negatives.jsonl
|
| 9 |
+
"""
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
import argparse
|
| 12 |
+
import json
|
| 13 |
+
import re
|
| 14 |
+
from collections import Counter
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
ROOT = Path(__file__).resolve().parents[3]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def diff_segments(pred_segs: list, gold_segs: list) -> dict:
|
| 21 |
+
out = {"seg_count_match": len(pred_segs) == len(gold_segs)}
|
| 22 |
+
out["pred_n"] = len(pred_segs)
|
| 23 |
+
out["gold_n"] = len(gold_segs)
|
| 24 |
+
pred_intents = [s.get("intent") for s in pred_segs]
|
| 25 |
+
gold_intents = [s.get("intent") for s in gold_segs]
|
| 26 |
+
out["intent_match_ordered"] = pred_intents == gold_intents
|
| 27 |
+
out["intent_overlap"] = list(Counter(gold_intents) & Counter(pred_intents))
|
| 28 |
+
return out
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def main():
|
| 32 |
+
p = argparse.ArgumentParser(description=__doc__)
|
| 33 |
+
p.add_argument("dump")
|
| 34 |
+
p.add_argument("--out", default="poc/llm-finetune/training/data_canonical_v2/hard_negatives.jsonl")
|
| 35 |
+
args = p.parse_args()
|
| 36 |
+
|
| 37 |
+
dump = json.loads(Path(args.dump).read_text())
|
| 38 |
+
rows = dump["rows"]
|
| 39 |
+
|
| 40 |
+
failures = []
|
| 41 |
+
type_counter = Counter()
|
| 42 |
+
for r in rows:
|
| 43 |
+
gold = r.get("gold") or {}
|
| 44 |
+
pred = r.get("parsed") or {}
|
| 45 |
+
gold_segs = gold.get("segments", [])
|
| 46 |
+
pred_segs = pred.get("segments") or []
|
| 47 |
+
diff = diff_segments(pred_segs, gold_segs)
|
| 48 |
+
|
| 49 |
+
if diff["intent_match_ordered"]:
|
| 50 |
+
continue # not a failure on intent
|
| 51 |
+
# tag failure
|
| 52 |
+
if not diff["seg_count_match"]:
|
| 53 |
+
failure_type = f"seg_count_{diff['pred_n']}_vs_{diff['gold_n']}"
|
| 54 |
+
elif not diff["intent_overlap"]:
|
| 55 |
+
failure_type = "intent_disjoint"
|
| 56 |
+
else:
|
| 57 |
+
failure_type = "intent_order_or_partial_overlap"
|
| 58 |
+
type_counter[failure_type] += 1
|
| 59 |
+
failures.append({
|
| 60 |
+
"text": r["text"],
|
| 61 |
+
"compound": r.get("compound", False),
|
| 62 |
+
"gold_segments": gold_segs,
|
| 63 |
+
"pred_segments_for_audit": pred_segs,
|
| 64 |
+
"failure_type": failure_type,
|
| 65 |
+
})
|
| 66 |
+
|
| 67 |
+
print(f"loaded {len(rows)} rows; failures: {len(failures)}")
|
| 68 |
+
print("\nfailure type distribution:")
|
| 69 |
+
for ft, n in type_counter.most_common():
|
| 70 |
+
print(f" {ft:35s} {n}")
|
| 71 |
+
|
| 72 |
+
out_path = Path(args.out)
|
| 73 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 74 |
+
with out_path.open("w") as f:
|
| 75 |
+
for fail in failures:
|
| 76 |
+
f.write(json.dumps(fail, ensure_ascii=False) + "\n")
|
| 77 |
+
print(f"\nwrote {len(failures)} hard negatives -> {out_path}")
|
| 78 |
+
print(f"\nnext: feed back into build_canonical_v1.py via mine_v9_failures() helper, retrain V10")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
if __name__ == "__main__":
|
| 82 |
+
main()
|