Target related hard-negative contexts
This commit is contained in:
@@ -207,3 +207,35 @@ def test_region_cap_drops_only_repeats_and_preserves_every_unique_tile() -> None
|
|||||||
assert metadata["sampled_entries_by_region"]["flanders"] == 7
|
assert metadata["sampled_entries_by_region"]["flanders"] == 7
|
||||||
assert metadata["dropped_region_repeat_count"] == 13
|
assert metadata["dropped_region_repeat_count"] == 13
|
||||||
assert metadata["sampled_entries_by_region"]["flanders"] / len(paths) <= .65
|
assert metadata["sampled_entries_by_region"]["flanders"] / len(paths) <= .65
|
||||||
|
|
||||||
|
|
||||||
|
def test_coastal_precision_failure_targets_port_and_dunes_negatives() -> None:
|
||||||
|
manifest = {"samples": [
|
||||||
|
{"sample_slug": "coastal-train", "split": "train", "region": "flanders", "context": "coastal-urban"},
|
||||||
|
{"sample_slug": "port-negative", "split": "train", "region": "flanders", "context": "port-hard-negative"},
|
||||||
|
{"sample_slug": "dunes-negative", "split": "train", "region": "flanders", "context": "dunes-negative"},
|
||||||
|
{"sample_slug": "coastal-cal", "split": "calibration", "region": "flanders", "context": "coastal-urban"},
|
||||||
|
]}
|
||||||
|
summary = {"tiles": [
|
||||||
|
{"sample_slug": "coastal-train", "split": "train", "label_count": 2, "image_path": "/tmp/coastal.png"},
|
||||||
|
{"sample_slug": "port-negative", "split": "train", "label_count": 0, "image_path": "/tmp/port.png"},
|
||||||
|
{"sample_slug": "dunes-negative", "split": "train", "label_count": 0, "image_path": "/tmp/dunes.png"},
|
||||||
|
]}
|
||||||
|
assessment = {
|
||||||
|
"status": "continue_training_loop",
|
||||||
|
"gates": {"min_region_f1": .45, "min_region_precision": .5, "min_region_recall": .4,
|
||||||
|
"max_pure_empty_false_positives": 0},
|
||||||
|
"calibration": {
|
||||||
|
"regions": {"flanders": {"f1": .3, "precision": .2, "recall": .4}},
|
||||||
|
"samples": {"coastal-cal": {"f1": .1, "precision": .05, "recall": .2}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, metadata = MODULE.build_sampling(
|
||||||
|
summary=summary, manifest=manifest, assessment=assessment, max_region_share=1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
assert paths.count(str(Path("/tmp/port.png").resolve())) == 6
|
||||||
|
assert paths.count(str(Path("/tmp/dunes.png").resolve())) == 6
|
||||||
|
assert "flanders:port-hard-negative" in metadata["targeted_negative_contexts"]
|
||||||
|
assert "flanders:dunes-negative" in metadata["targeted_negative_contexts"]
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ The deterministic cap removes only repeated entries and retains every unique
|
|||||||
train tile at least once; manifests record pre-cap counts, final counts and the
|
train tile at least once; manifests record pre-cap counts, final counts and the
|
||||||
number of dropped repeats. This keeps a weak region prominent without turning
|
number of dropped repeats. This keeps a weak region prominent without turning
|
||||||
the national detector into a single-region expert.
|
the national detector into a single-region expert.
|
||||||
|
Precision correction expands failed semantic contexts into related negative
|
||||||
|
families: coastal urban failures target port/dunes negatives, industrial
|
||||||
|
failures target industrial/rail/port negatives, and ribbon/rural/regional
|
||||||
|
architecture failures target their governed farmland, forest or quarry
|
||||||
|
counterparts. These diagnostic negative repeats are ordered ahead of generic
|
||||||
|
repeats so the regional cap cannot discard them first.
|
||||||
The checkpointed orchestrator invokes this builder after every rejected
|
The checkpointed orchestrator invokes this builder after every rejected
|
||||||
iteration, stores its checksum in `training-loop-state.json`, and uses the
|
iteration, stores its checksum in `training-loop-state.json`, and uses the
|
||||||
resulting dataset YAML for the next checkpoint. A restart resumes both the
|
resulting dataset YAML for the next checkpoint. A restart resumes both the
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
sampling assigned 75.5% of entries to Flanders. The cap retains every unique
|
sampling assigned 75.5% of entries to Flanders. The cap retains every unique
|
||||||
tile, removes repeats only and writes pre/post regional counts into the
|
tile, removes repeats only and writes pre/post regional counts into the
|
||||||
checksummed sampling evidence.
|
checksummed sampling evidence.
|
||||||
|
- Added semantic hard-negative families after the v31 calibration isolated
|
||||||
|
Oostende coastal/port precision as the dominant Flemish error. Coastal,
|
||||||
|
industrial, ribbon, rural and regional-architecture failures now target
|
||||||
|
related train-only negative contexts, with diagnostic repeats preserved
|
||||||
|
ahead of generic repeats when the regional cap applies.
|
||||||
|
|
||||||
## 2026-07-27 - Guest demo and product professionalization
|
## 2026-07-27 - Guest demo and product professionalization
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
PRECISION_NEGATIVE_CONTEXTS = {
|
||||||
|
"coastal-urban": {"port-hard-negative", "dunes-negative"},
|
||||||
|
"industrial": {"industrial-hard-negative", "rail-hard-negative", "port-hard-negative"},
|
||||||
|
"ribbon-development": {"farmland-hard-negative", "forest-hard-negative"},
|
||||||
|
"rural-town": {"farmland-hard-negative", "forest-hard-negative", "quarry-hard-negative"},
|
||||||
|
"regional-architecture": {"forest-hard-negative", "quarry-hard-negative"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def file_sha256(path: Path) -> str:
|
def file_sha256(path: Path) -> str:
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
with path.open("rb") as stream:
|
with path.open("rb") as stream:
|
||||||
@@ -94,6 +103,12 @@ def build_sampling(
|
|||||||
and metrics["precision"] < gates["min_region_precision"]
|
and metrics["precision"] < gates["min_region_precision"]
|
||||||
):
|
):
|
||||||
weak_precision_contexts.add(key)
|
weak_precision_contexts.add(key)
|
||||||
|
targeted_negative_contexts = set(weak_precision_contexts)
|
||||||
|
for region, context in weak_precision_contexts:
|
||||||
|
targeted_negative_contexts.update(
|
||||||
|
(region, related)
|
||||||
|
for related in PRECISION_NEGATIVE_CONTEXTS.get(context, set())
|
||||||
|
)
|
||||||
|
|
||||||
base_paths_by_region: dict[str, list[str]] = {}
|
base_paths_by_region: dict[str, list[str]] = {}
|
||||||
extra_paths_by_region: dict[str, list[str]] = {}
|
extra_paths_by_region: dict[str, list[str]] = {}
|
||||||
@@ -120,12 +135,19 @@ def build_sampling(
|
|||||||
if tile["label_count"] == 0 and (background_failed or region in weak_precision_regions):
|
if tile["label_count"] == 0 and (background_failed or region in weak_precision_regions):
|
||||||
repeat = (
|
repeat = (
|
||||||
context_negative_repeat
|
context_negative_repeat
|
||||||
if context_key in weak_precision_contexts
|
if context_key in targeted_negative_contexts
|
||||||
else negative_repeat
|
else negative_repeat
|
||||||
)
|
)
|
||||||
path = str(Path(tile["image_path"]).resolve())
|
path = str(Path(tile["image_path"]).resolve())
|
||||||
base_paths_by_region.setdefault(region, []).append(path)
|
base_paths_by_region.setdefault(region, []).append(path)
|
||||||
extra_paths_by_region.setdefault(region, []).extend([path] * (repeat - 1))
|
extras = extra_paths_by_region.setdefault(region, [])
|
||||||
|
repeated = [path] * (repeat - 1)
|
||||||
|
if tile["label_count"] == 0 and context_key in targeted_negative_contexts:
|
||||||
|
# Preserve the most diagnostic hard-negative repeats when the
|
||||||
|
# regional cap has to remove lower-priority repetition.
|
||||||
|
extras[:0] = repeated
|
||||||
|
else:
|
||||||
|
extras.extend(repeated)
|
||||||
selected_samples.add(tile["sample_slug"])
|
selected_samples.add(tile["sample_slug"])
|
||||||
|
|
||||||
pre_cap_counts = Counter({
|
pre_cap_counts = Counter({
|
||||||
@@ -163,6 +185,9 @@ def build_sampling(
|
|||||||
"weak_precision_regions": sorted(weak_precision_regions),
|
"weak_precision_regions": sorted(weak_precision_regions),
|
||||||
"weak_recall_contexts": [f"{region}:{context}" for region, context in sorted(weak_recall_contexts)],
|
"weak_recall_contexts": [f"{region}:{context}" for region, context in sorted(weak_recall_contexts)],
|
||||||
"weak_precision_contexts": [f"{region}:{context}" for region, context in sorted(weak_precision_contexts)],
|
"weak_precision_contexts": [f"{region}:{context}" for region, context in sorted(weak_precision_contexts)],
|
||||||
|
"targeted_negative_contexts": [
|
||||||
|
f"{region}:{context}" for region, context in sorted(targeted_negative_contexts)
|
||||||
|
],
|
||||||
"background_gate_failed": background_failed,
|
"background_gate_failed": background_failed,
|
||||||
"positive_repeat": positive_repeat,
|
"positive_repeat": positive_repeat,
|
||||||
"negative_repeat": negative_repeat,
|
"negative_repeat": negative_repeat,
|
||||||
|
|||||||
Reference in New Issue
Block a user