Fix: repair JSON-escaped newlines in training data (v1/v2 scored 0% ast_rate vs base 93%)
Browse files- train_securecoder.py +30 -3
train_securecoder.py
CHANGED
|
@@ -582,6 +582,29 @@ def render_record(tokenizer, messages: list[dict], tools: list[dict] | None = No
|
|
| 582 |
|
| 583 |
|
| 584 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
def build_dataset(tokenizer, sources: list[Source], token: str | None, validate: bool):
|
| 586 |
"""Returns (records, stats). ``records`` are {"text", "source"} dicts."""
|
| 587 |
records: list[dict] = []
|
|
@@ -589,7 +612,7 @@ def build_dataset(tokenizer, sources: list[Source], token: str | None, validate:
|
|
| 589 |
|
| 590 |
for src in sources:
|
| 591 |
entry = {"source": source_name(src), "note": src.note, "kept": 0, "skipped": 0,
|
| 592 |
-
"tool_samples": 0, "chars": 0, "error": None}
|
| 593 |
try:
|
| 594 |
rows = load_source(src, token, progress=validate)
|
| 595 |
for row in rows:
|
|
@@ -603,6 +626,7 @@ def build_dataset(tokenizer, sources: list[Source], token: str | None, validate:
|
|
| 603 |
continue
|
| 604 |
try:
|
| 605 |
text = render_record(tokenizer, messages, tools)
|
|
|
|
| 606 |
except Exception as exc: # noqa: BLE001 - bad template input, skip row
|
| 607 |
if entry["skipped"] < 3:
|
| 608 |
log.warning(" render failed (%s): %s", source_name(src), exc)
|
|
@@ -614,6 +638,8 @@ def build_dataset(tokenizer, sources: list[Source], token: str | None, validate:
|
|
| 614 |
records.append({"text": text, "source": source_name(src)})
|
| 615 |
entry["kept"] += 1
|
| 616 |
entry["chars"] += len(text)
|
|
|
|
|
|
|
| 617 |
if tools:
|
| 618 |
entry["tool_samples"] += 1
|
| 619 |
except Exception as exc: # noqa: BLE001 - one bad dataset must not kill the run
|
|
@@ -621,8 +647,9 @@ def build_dataset(tokenizer, sources: list[Source], token: str | None, validate:
|
|
| 621 |
log.error(" %s failed: %s", source_name(src), exc)
|
| 622 |
|
| 623 |
stats.append(entry)
|
| 624 |
-
log.info(" %-58s kept=%-6d skipped=%-5d tools=%-5d",
|
| 625 |
-
entry["source"], entry["kept"], entry["skipped"], entry["tool_samples"]
|
|
|
|
| 626 |
|
| 627 |
random.shuffle(records)
|
| 628 |
return records, stats
|
|
|
|
| 582 |
|
| 583 |
|
| 584 |
|
| 585 |
+
def _unescape_if_needed(text: str) -> tuple[str, bool]:
|
| 586 |
+
"""Repair JSON-escaped newlines before training.
|
| 587 |
+
|
| 588 |
+
Parts of the data mix store message text already JSON-escaped, so the rendered
|
| 589 |
+
training text carries the two-character sequence backslash+n instead of a real
|
| 590 |
+
newline. Fine-tuning on that teaches the model to emit a literal "\\n", which is
|
| 591 |
+
why both v1 and v2 scored 0% ast_rate while the untouched base model scored 93%.
|
| 592 |
+
|
| 593 |
+
Only fires when escaped newlines clearly dominate, so ordinary code samples that
|
| 594 |
+
legitimately contain a backslash are left untouched.
|
| 595 |
+
"""
|
| 596 |
+
literal = text.count("\\n")
|
| 597 |
+
if literal == 0:
|
| 598 |
+
return text, False
|
| 599 |
+
if text.count("\n") > literal:
|
| 600 |
+
return text, False
|
| 601 |
+
fixed = (text.replace("\\r\\n", "\n")
|
| 602 |
+
.replace("\\n", "\n")
|
| 603 |
+
.replace("\\t", "\t")
|
| 604 |
+
.replace('\\"', '"'))
|
| 605 |
+
return fixed, True
|
| 606 |
+
|
| 607 |
+
|
| 608 |
def build_dataset(tokenizer, sources: list[Source], token: str | None, validate: bool):
|
| 609 |
"""Returns (records, stats). ``records`` are {"text", "source"} dicts."""
|
| 610 |
records: list[dict] = []
|
|
|
|
| 612 |
|
| 613 |
for src in sources:
|
| 614 |
entry = {"source": source_name(src), "note": src.note, "kept": 0, "skipped": 0,
|
| 615 |
+
"tool_samples": 0, "chars": 0, "repaired": 0, "error": None}
|
| 616 |
try:
|
| 617 |
rows = load_source(src, token, progress=validate)
|
| 618 |
for row in rows:
|
|
|
|
| 626 |
continue
|
| 627 |
try:
|
| 628 |
text = render_record(tokenizer, messages, tools)
|
| 629 |
+
text, repaired = _unescape_if_needed(text)
|
| 630 |
except Exception as exc: # noqa: BLE001 - bad template input, skip row
|
| 631 |
if entry["skipped"] < 3:
|
| 632 |
log.warning(" render failed (%s): %s", source_name(src), exc)
|
|
|
|
| 638 |
records.append({"text": text, "source": source_name(src)})
|
| 639 |
entry["kept"] += 1
|
| 640 |
entry["chars"] += len(text)
|
| 641 |
+
if repaired:
|
| 642 |
+
entry["repaired"] += 1
|
| 643 |
if tools:
|
| 644 |
entry["tool_samples"] += 1
|
| 645 |
except Exception as exc: # noqa: BLE001 - one bad dataset must not kill the run
|
|
|
|
| 647 |
log.error(" %s failed: %s", source_name(src), exc)
|
| 648 |
|
| 649 |
stats.append(entry)
|
| 650 |
+
log.info(" %-58s kept=%-6d skipped=%-5d tools=%-5d repaired=%-5d",
|
| 651 |
+
entry["source"], entry["kept"], entry["skipped"], entry["tool_samples"],
|
| 652 |
+
entry.get("repaired", 0))
|
| 653 |
|
| 654 |
random.shuffle(records)
|
| 655 |
return records, stats
|