Spaces:
Running
Running
Randomize candidate dataset order
Browse files- dataset/README.md +1 -1
- dataset/scripts/export_training_data.py +20 -0
- dataset/scripts/generate_samples.py +59 -17
dataset/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Verify:
|
|
| 103 |
|
| 104 |
```bash
|
| 105 |
modal volume ls gazet-data
|
| 106 |
-
# should show:
|
| 107 |
```
|
| 108 |
|
| 109 |
Skip this step on subsequent runs — the volume persists across runs.
|
|
|
|
| 103 |
|
| 104 |
```bash
|
| 105 |
modal volume ls gazet-data
|
| 106 |
+
# should show: overture_normalized/, natural_earth_normalized/
|
| 107 |
```
|
| 108 |
|
| 109 |
Skip this step on subsequent runs — the volume persists across runs.
|
dataset/scripts/export_training_data.py
CHANGED
|
@@ -20,6 +20,7 @@ Output layout (all paths relative to dataset/):
|
|
| 20 |
output/runs/{run_name}/stats.json
|
| 21 |
"""
|
| 22 |
|
|
|
|
| 23 |
import json
|
| 24 |
import random
|
| 25 |
import sys
|
|
@@ -178,6 +179,23 @@ def _format_sql(sql: str) -> str:
|
|
| 178 |
).strip()
|
| 179 |
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
def sample_to_sql_pair(sample: Dict[str, Any]) -> Optional[Dict]:
|
| 182 |
"""Convert a raw sample to a conversational prompt-completion pair for SQL generation."""
|
| 183 |
sql = sample.get("target", {}).get("sql", "").strip()
|
|
@@ -360,7 +378,9 @@ def main(config_path: Optional[Path] = None) -> None:
|
|
| 360 |
# Load
|
| 361 |
print("\nLoading validated samples...")
|
| 362 |
samples = load_samples(validated_file)
|
|
|
|
| 363 |
print(f" {len(samples):,} samples loaded")
|
|
|
|
| 364 |
|
| 365 |
# Split once, reuse for both tasks
|
| 366 |
print("\nSplitting 80 / 10 / 10 (stratified by task family)...")
|
|
|
|
| 20 |
output/runs/{run_name}/stats.json
|
| 21 |
"""
|
| 22 |
|
| 23 |
+
import copy
|
| 24 |
import json
|
| 25 |
import random
|
| 26 |
import sys
|
|
|
|
| 179 |
).strip()
|
| 180 |
|
| 181 |
|
| 182 |
+
def _shuffle_candidates_for_export(sample: Dict[str, Any]) -> Dict[str, Any]:
|
| 183 |
+
"""Return a copy of sample with candidate row order shuffled deterministically.
|
| 184 |
+
|
| 185 |
+
Candidate IDs remain unchanged; only row order changes. This removes the
|
| 186 |
+
positional shortcut where the true anchor often appears first in the raw
|
| 187 |
+
synthetic samples, while keeping selected_candidates valid.
|
| 188 |
+
"""
|
| 189 |
+
shuffled = copy.deepcopy(sample)
|
| 190 |
+
candidates = shuffled.get("candidates", [])
|
| 191 |
+
if len(candidates) <= 1:
|
| 192 |
+
return shuffled
|
| 193 |
+
|
| 194 |
+
rng = random.Random(shuffled.get("id", "sample"))
|
| 195 |
+
rng.shuffle(candidates)
|
| 196 |
+
return shuffled
|
| 197 |
+
|
| 198 |
+
|
| 199 |
def sample_to_sql_pair(sample: Dict[str, Any]) -> Optional[Dict]:
|
| 200 |
"""Convert a raw sample to a conversational prompt-completion pair for SQL generation."""
|
| 201 |
sql = sample.get("target", {}).get("sql", "").strip()
|
|
|
|
| 378 |
# Load
|
| 379 |
print("\nLoading validated samples...")
|
| 380 |
samples = load_samples(validated_file)
|
| 381 |
+
samples = [_shuffle_candidates_for_export(s) for s in samples]
|
| 382 |
print(f" {len(samples):,} samples loaded")
|
| 383 |
+
print(" Candidate row order shuffled deterministically for export")
|
| 384 |
|
| 385 |
# Split once, reuse for both tasks
|
| 386 |
print("\nSplitting 80 / 10 / 10 (stratified by task family)...")
|
dataset/scripts/generate_samples.py
CHANGED
|
@@ -416,6 +416,35 @@ def _merge_candidate_lists(
|
|
| 416 |
return merged
|
| 417 |
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
def build_candidate_list(
|
| 420 |
con: duckdb.DuckDBPyConnection,
|
| 421 |
anchor_id: str,
|
|
@@ -1008,19 +1037,29 @@ def generate_template_based_sample(
|
|
| 1008 |
)
|
| 1009 |
|
| 1010 |
elif template.template_id in ("contain_multi_01", "contain_multi_02", "contain_multi_03"):
|
| 1011 |
-
# country IN clause — 2 or 3 anchors, each contributes its country code
|
|
|
|
|
|
|
| 1012 |
num_a = 3 if template.template_id == "contain_multi_02" else 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
anchors = [
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
'
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
|
|
|
| 1021 |
]
|
| 1022 |
-
if any(a is None for a in anchors):
|
| 1023 |
-
return None
|
| 1024 |
|
| 1025 |
countries = [a.get('country') or 'US' for a in anchors]
|
| 1026 |
target_subtype = template.target_subtype or 'region'
|
|
@@ -1039,7 +1078,10 @@ def generate_template_based_sample(
|
|
| 1039 |
num_candidates=per_anchor, difficulty="medium")
|
| 1040 |
for a in anchors
|
| 1041 |
]
|
| 1042 |
-
candidates =
|
|
|
|
|
|
|
|
|
|
| 1043 |
|
| 1044 |
q_kwargs = dict(target_subtype=target_subtype)
|
| 1045 |
for i, a in enumerate(anchors, 1):
|
|
@@ -1629,7 +1671,12 @@ def generate_template_based_sample(
|
|
| 1629 |
template.anchor_source == "mixed" and template.num_anchors == 2
|
| 1630 |
)
|
| 1631 |
|
| 1632 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1633 |
anchor_ids: set = set()
|
| 1634 |
for var in ("anchor1", "anchor2", "anchor3"):
|
| 1635 |
obj = locals().get(var)
|
|
@@ -1641,11 +1688,6 @@ def generate_template_based_sample(
|
|
| 1641 |
anchor_ids.add(a.get("id", ""))
|
| 1642 |
selected_candidate_ids = [c.candidate_id for c in candidates if c.id in anchor_ids]
|
| 1643 |
|
| 1644 |
-
elif _is_mixed_two_anchor:
|
| 1645 |
-
# partial_05 / diff_02: anchor (division) + clip_feature (natural_earth)
|
| 1646 |
-
mixed_ids = {anchor.get("id", ""), clip_feature.get("id", "")}
|
| 1647 |
-
selected_candidate_ids = [c.candidate_id for c in candidates if c.id in mixed_ids]
|
| 1648 |
-
|
| 1649 |
else:
|
| 1650 |
anchor_id_to_find = (
|
| 1651 |
anchor.get('anchor_id')
|
|
|
|
| 416 |
return merged
|
| 417 |
|
| 418 |
|
| 419 |
+
def _dedupe_country_candidates(
|
| 420 |
+
candidates: List[Candidate],
|
| 421 |
+
max_total: Optional[int] = None,
|
| 422 |
+
) -> List[Candidate]:
|
| 423 |
+
"""Deduplicate country candidates by country code, preserving first match.
|
| 424 |
+
|
| 425 |
+
This is useful for templates whose SQL uses ``country IN (...)`` rather
|
| 426 |
+
than candidate IDs. Overture can contain multiple country-level rows for
|
| 427 |
+
the same ISO code, which weakens grounding if they all remain in the list.
|
| 428 |
+
"""
|
| 429 |
+
deduped: List[Candidate] = []
|
| 430 |
+
seen_keys: set[tuple[str, str]] = set()
|
| 431 |
+
for cand in candidates:
|
| 432 |
+
if cand.subtype == "country" and cand.country:
|
| 433 |
+
key = ("country", cand.country)
|
| 434 |
+
else:
|
| 435 |
+
key = ("id", cand.id)
|
| 436 |
+
if key in seen_keys:
|
| 437 |
+
continue
|
| 438 |
+
deduped.append(cand)
|
| 439 |
+
seen_keys.add(key)
|
| 440 |
+
if max_total is not None and len(deduped) >= max_total:
|
| 441 |
+
break
|
| 442 |
+
|
| 443 |
+
for i, cand in enumerate(deduped, 1):
|
| 444 |
+
cand.candidate_id = f"c{i}"
|
| 445 |
+
return deduped
|
| 446 |
+
|
| 447 |
+
|
| 448 |
def build_candidate_list(
|
| 449 |
con: duckdb.DuckDBPyConnection,
|
| 450 |
anchor_id: str,
|
|
|
|
| 1037 |
)
|
| 1038 |
|
| 1039 |
elif template.template_id in ("contain_multi_01", "contain_multi_02", "contain_multi_03"):
|
| 1040 |
+
# country IN clause — 2 or 3 anchors, each contributes its country code.
|
| 1041 |
+
# Sample unique countries so the query actually teaches a multi-country
|
| 1042 |
+
# pattern rather than repeating the same ISO code multiple times.
|
| 1043 |
num_a = 3 if template.template_id == "contain_multi_02" else 2
|
| 1044 |
+
country_inventory = tables['divisions_area_inventory']
|
| 1045 |
+
country_inventory = country_inventory[
|
| 1046 |
+
(country_inventory['subtype'] == 'country')
|
| 1047 |
+
& country_inventory['country'].notna()
|
| 1048 |
+
].drop_duplicates(subset=['country'])
|
| 1049 |
+
if len(country_inventory) < num_a:
|
| 1050 |
+
return None
|
| 1051 |
+
|
| 1052 |
+
sampled = country_inventory.sample(n=num_a, replace=False)
|
| 1053 |
anchors = [
|
| 1054 |
+
{
|
| 1055 |
+
'id': row['id'],
|
| 1056 |
+
'name': row['name'],
|
| 1057 |
+
'subtype': row.get('subtype'),
|
| 1058 |
+
'country': row.get('country'),
|
| 1059 |
+
'source': 'divisions_area',
|
| 1060 |
+
}
|
| 1061 |
+
for _, row in sampled.iterrows()
|
| 1062 |
]
|
|
|
|
|
|
|
| 1063 |
|
| 1064 |
countries = [a.get('country') or 'US' for a in anchors]
|
| 1065 |
target_subtype = template.target_subtype or 'region'
|
|
|
|
| 1078 |
num_candidates=per_anchor, difficulty="medium")
|
| 1079 |
for a in anchors
|
| 1080 |
]
|
| 1081 |
+
candidates = _dedupe_country_candidates(
|
| 1082 |
+
_merge_candidate_lists(*cands, max_total=num_a * per_anchor),
|
| 1083 |
+
max_total=num_a * per_anchor,
|
| 1084 |
+
)
|
| 1085 |
|
| 1086 |
q_kwargs = dict(target_subtype=target_subtype)
|
| 1087 |
for i, a in enumerate(anchors, 1):
|
|
|
|
| 1671 |
template.anchor_source == "mixed" and template.num_anchors == 2
|
| 1672 |
)
|
| 1673 |
|
| 1674 |
+
if _is_mixed_two_anchor:
|
| 1675 |
+
# partial_05 / diff_02: anchor (division) + clip_feature (natural_earth)
|
| 1676 |
+
mixed_ids = {anchor.get("id", ""), clip_feature.get("id", "")}
|
| 1677 |
+
selected_candidate_ids = [c.candidate_id for c in candidates if c.id in mixed_ids]
|
| 1678 |
+
|
| 1679 |
+
elif template.family in _multi_anchor_families and template.num_anchors >= 2:
|
| 1680 |
anchor_ids: set = set()
|
| 1681 |
for var in ("anchor1", "anchor2", "anchor3"):
|
| 1682 |
obj = locals().get(var)
|
|
|
|
| 1688 |
anchor_ids.add(a.get("id", ""))
|
| 1689 |
selected_candidate_ids = [c.candidate_id for c in candidates if c.id in anchor_ids]
|
| 1690 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1691 |
else:
|
| 1692 |
anchor_id_to_find = (
|
| 1693 |
anchor.get('anchor_id')
|