Target related hard-negative contexts
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-29 18:27:54 +02:00
parent a56df8b1ed
commit cfce3e64ec
4 changed files with 70 additions and 2 deletions
@@ -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["dropped_region_repeat_count"] == 13
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"]
+6
View File
@@ -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
number of dropped repeats. This keeps a weak region prominent without turning
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
iteration, stores its checksum in `training-loop-state.json`, and uses the
resulting dataset YAML for the next checkpoint. A restart resumes both the
+5
View File
@@ -19,6 +19,11 @@
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
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
+27 -2
View File
@@ -12,6 +12,15 @@ from pathlib import Path
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:
digest = hashlib.sha256()
with path.open("rb") as stream:
@@ -94,6 +103,12 @@ def build_sampling(
and metrics["precision"] < gates["min_region_precision"]
):
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]] = {}
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):
repeat = (
context_negative_repeat
if context_key in weak_precision_contexts
if context_key in targeted_negative_contexts
else negative_repeat
)
path = str(Path(tile["image_path"]).resolve())
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"])
pre_cap_counts = Counter({
@@ -163,6 +185,9 @@ def build_sampling(
"weak_precision_regions": sorted(weak_precision_regions),
"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)],
"targeted_negative_contexts": [
f"{region}:{context}" for region, context in sorted(targeted_negative_contexts)
],
"background_gate_failed": background_failed,
"positive_repeat": positive_repeat,
"negative_repeat": negative_repeat,