feat: harden governed PyTorch training programme
This commit is contained in:
@@ -55,6 +55,7 @@ def test_operator_yolo_train_smoke_script_contract() -> None:
|
|||||||
script = script_path.read_text(encoding="utf-8")
|
script = script_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert "bash -n scripts/train_operator_yolo_detector.sh" in readiness
|
assert "bash -n scripts/train_operator_yolo_detector.sh" in readiness
|
||||||
|
assert "TRAIN_REQUIRE_CUDA" in script_path.read_text(encoding="utf-8")
|
||||||
assert "OPERATOR_YOLO_DATASET_DIR" in script
|
assert "OPERATOR_YOLO_DATASET_DIR" in script
|
||||||
assert "YOLO_BASE_MODEL_PATH" in script
|
assert "YOLO_BASE_MODEL_PATH" in script
|
||||||
assert "TRAIN_MODEL_OUTPUT_PATH" in script
|
assert "TRAIN_MODEL_OUTPUT_PATH" in script
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ def test_operator_yolo_tile_dataset_export_help_does_not_require_gis_dependencie
|
|||||||
assert "--background-negative-repeat" in result.stdout
|
assert "--background-negative-repeat" in result.stdout
|
||||||
assert "--drop-low-variance-negatives" in result.stdout
|
assert "--drop-low-variance-negatives" in result.stdout
|
||||||
assert "--blank-range-threshold" in result.stdout
|
assert "--blank-range-threshold" in result.stdout
|
||||||
|
assert "--class-name" in result.stdout
|
||||||
|
assert "--reference-source" in result.stdout
|
||||||
|
assert "--reference-layer" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_default_validation_split_is_explicit_and_rejects_holdout_leakage() -> None:
|
def test_default_validation_split_is_explicit_and_rejects_holdout_leakage() -> None:
|
||||||
@@ -305,7 +308,11 @@ def test_export_can_skip_low_variance_negative_tiles(tmp_path: Path, monkeypatch
|
|||||||
|
|
||||||
monkeypatch.setattr(module, "rasterio", FakeRasterio)
|
monkeypatch.setattr(module, "rasterio", FakeRasterio)
|
||||||
monkeypatch.setattr(module, "Image", FakeImage)
|
monkeypatch.setattr(module, "Image", FakeImage)
|
||||||
monkeypatch.setattr(module, "load_reference_pixel_boxes", lambda reference_path, dataset, min_label_px: [])
|
monkeypatch.setattr(
|
||||||
|
module,
|
||||||
|
"load_reference_pixel_boxes",
|
||||||
|
lambda reference_path, dataset, min_label_px, **kwargs: [],
|
||||||
|
)
|
||||||
monkeypatch.setattr(module, "image_array_from_raster_window", fake_image_array_from_raster_window)
|
monkeypatch.setattr(module, "image_array_from_raster_window", fake_image_array_from_raster_window)
|
||||||
|
|
||||||
records = module.export_sample_tiles(
|
records = module.export_sample_tiles(
|
||||||
@@ -327,6 +334,8 @@ def test_export_can_skip_low_variance_negative_tiles(tmp_path: Path, monkeypatch
|
|||||||
background_negative_repeat=1,
|
background_negative_repeat=1,
|
||||||
drop_low_variance_negatives=True,
|
drop_low_variance_negatives=True,
|
||||||
blank_range_threshold=3,
|
blank_range_threshold=3,
|
||||||
|
reference_source="grb",
|
||||||
|
reference_layer="buildings",
|
||||||
)
|
)
|
||||||
|
|
||||||
skipped = [record for record in records if not record["kept"]]
|
skipped = [record for record in records if not record["kept"]]
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# AI Pipelines
|
# AI Pipelines
|
||||||
|
|
||||||
|
The cross-task PyTorch scope, capability matrix and national promotion waves are
|
||||||
|
defined in `docs/PYTORCH_MODEL_PROGRAM.md`. PyTorch is used only for trainable
|
||||||
|
imagery tasks; authoritative GIS measurements remain source-derived. The
|
||||||
|
single-class tile exporter accepts explicit `--class-name`,
|
||||||
|
`--reference-source` and `--reference-layer` bindings and persists them in its
|
||||||
|
evidence summary. Production Tower training uses `TRAIN_DEVICE=cuda:0` with
|
||||||
|
`TRAIN_REQUIRE_CUDA=true`, which fails closed without CUDA and records the
|
||||||
|
PyTorch/CUDA runtime in `training_summary.json`.
|
||||||
|
|
||||||
## 1. Object Detection Pipeline
|
## 1. Object Detection Pipeline
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
## 2026-07-26 - PyTorch programme clarification and training hardening
|
||||||
|
|
||||||
|
- Clarified that PyTorch governs trainable imagery models and does not replace authoritative terrain, flood, land-use, road, water or change analyses.
|
||||||
|
- Audited Tower: CUDA PyTorch runs on the RTX 4080 SUPER; only the building corpus currently has promotion evidence. The active building candidate measures roughly 0.61 mean F1 on the expanded independent portfolio and zero detections on three pure-empty background samples.
|
||||||
|
- Generalized the tile exporter with explicit canonical class, reference source and reference layer evidence so regional authorities cannot be silently mixed.
|
||||||
|
- Added fail-closed `TRAIN_REQUIRE_CUDA` behavior and PyTorch/CUDA runtime evidence to training summaries.
|
||||||
|
- Added `docs/PYTORCH_MODEL_PROGRAM.md` with the task matrix and national training waves.
|
||||||
|
- Verification: 14 focused exporter/training tests passed.
|
||||||
|
- Open: nationwide building training cannot honestly start until spatially disjoint, temporally compatible GRB/PICC/URBIS plus orthophoto samples have been materialized and reviewed.
|
||||||
|
|
||||||
## 2026-07-19 - GeoIntel 1.0.0 final release closeout
|
## 2026-07-19 - GeoIntel 1.0.0 final release closeout
|
||||||
|
|
||||||
- Promoted the current clean `main` revision to semantic version `1.0.0` in
|
- Promoted the current clean `main` revision to semantic version `1.0.0` in
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# PyTorch model programme
|
||||||
|
|
||||||
|
## Principle
|
||||||
|
|
||||||
|
PyTorch is the governed CUDA runtime for trainable image models. It is not a
|
||||||
|
replacement for authoritative GIS processing. Terrain, flood depth, land-use
|
||||||
|
classes, road length, water area and vector/raster change remain deterministic
|
||||||
|
source-derived analyses.
|
||||||
|
|
||||||
|
## Capability matrix
|
||||||
|
|
||||||
|
| Capability | Method | Current state | Promotion requirement |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Building localisation | YOLO detection on orthophoto | Operational for `building`; validated only in Mol/Kempen | geographically independent Belgian train/validation/test portfolio, including Flanders, Wallonia and Brussels |
|
||||||
|
| Building footprint | official GRB/PICC/URBIS geometry | Operational where the governed source covers the AOI | no neural model; source coverage and freshness gates |
|
||||||
|
| Roads and water | official vectors and thematic rasters | Operational by provider zone | no detector unless a separately justified imagery use case and reviewed labels exist |
|
||||||
|
| Land use / vegetation | official classified rasters | Operational where source coverage exists | no neural model; preserve official class semantics |
|
||||||
|
| Terrain / flood / bathymetry | numeric raster analysis | Operational or explicit `not_configured` per source | no neural model; source and CRS validation |
|
||||||
|
| Solar panels or other imagery objects | dedicated detector | Unvalidated local asset only | task-specific labels, negative corpus, independent hold-outs and promotion report |
|
||||||
|
| Segmentation | dedicated YOLO-seg/SAM model | `not_configured` | reviewed polygon/mask corpus and segmentation QA; detection boxes are not valid mask labels |
|
||||||
|
|
||||||
|
## Training rules
|
||||||
|
|
||||||
|
1. One model contract per task and canonical class set.
|
||||||
|
2. Training, validation and test samples are separated geographically; an AOI
|
||||||
|
or overlapping image tile may occur in only one split.
|
||||||
|
3. Reference labels identify authority, source, layer, observation/publication
|
||||||
|
date, CRS and checksum.
|
||||||
|
4. Production training requires NVIDIA CUDA and fails closed when unavailable.
|
||||||
|
5. Every challenger is evaluated on positive AOIs, pure-empty negatives and
|
||||||
|
difficult contextual negatives. Promotion is never based on training loss.
|
||||||
|
6. A model remains `not_configured` outside its independently proven geography
|
||||||
|
and classes.
|
||||||
|
|
||||||
|
## Executable waves
|
||||||
|
|
||||||
|
1. **Building Belgium corpus:** prepare spatially disjoint samples from GRB
|
||||||
|
(Flanders), PICC (Wallonia) and URBIS (Brussels) against temporally compatible
|
||||||
|
orthophotos. Keep complete municipalities outside training as test areas.
|
||||||
|
2. **Building challenger:** train on Tower using PyTorch CUDA, compare against
|
||||||
|
the active model at fixed thresholds and retain the current model unless all
|
||||||
|
promotion gates pass.
|
||||||
|
3. **Additional imagery task intake:** admit solar panels or another class only
|
||||||
|
after an explicit use case and reviewed labels exist. Never infer new classes
|
||||||
|
from the building corpus.
|
||||||
|
4. **Segmentation intake:** build a polygon/mask corpus and independent QA before
|
||||||
|
enabling YOLO-seg or SAM.
|
||||||
|
|
||||||
|
The tile exporter accepts explicit `--class-name`, `--reference-source` and
|
||||||
|
`--reference-layer` values and records them in dataset evidence. The production
|
||||||
|
training wrapper supports `TRAIN_REQUIRE_CUDA=true` and records CUDA and PyTorch
|
||||||
|
runtime evidence in every training summary.
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
# GeoIntel TODO
|
# GeoIntel TODO
|
||||||
|
|
||||||
|
## PyTorch-modelprogramma
|
||||||
|
|
||||||
|
- [x] Scheid trainbare beeldtaken van deterministische GIS-analyses.
|
||||||
|
- [x] Generaliseer de single-class tile-export op klasse, referentiebron en referentielaag.
|
||||||
|
- [x] Laat productietraining fail-closed stoppen wanneer vereiste CUDA ontbreekt.
|
||||||
|
- [ ] Materialiseer geografisch gescheiden GRB/PICC/URBIS-gebouwcorpora met temporeel passende orthofoto's.
|
||||||
|
- [ ] Train en evalueer een Belgische gebouwchallenger onafhankelijk; promoveer alleen zonder achtergrondregressie.
|
||||||
|
- [ ] Houd zonnepanelen en segmentatie `not_configured` tot gereviewde taaklabels en hold-outs bestaan.
|
||||||
|
|
||||||
## Actieve post-RC datadekkingsfase
|
## Actieve post-RC datadekkingsfase
|
||||||
|
|
||||||
- [x] Voeg een interactieve, data-gedreven projectatlas toe aan de statuswerkruimte met toegankelijke navigatie, echte readiness-toestanden en reduced-motion ondersteuning.
|
- [x] Voeg een interactieve, data-gedreven projectatlas toe aan de statuswerkruimte met toegankelijke navigatie, echte readiness-toestanden en reduced-motion ondersteuning.
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from typing import Any, Iterable
|
|||||||
|
|
||||||
DEFAULT_MANIFEST_PATH = Path("/app/storage/operator-data/operator_samples_manifest.json")
|
DEFAULT_MANIFEST_PATH = Path("/app/storage/operator-data/operator_samples_manifest.json")
|
||||||
DEFAULT_OUTPUT_DIR = Path("/app/storage/operator-data/yolo-building-tile-dataset")
|
DEFAULT_OUTPUT_DIR = Path("/app/storage/operator-data/yolo-building-tile-dataset")
|
||||||
|
DEFAULT_CLASS_NAME = "building"
|
||||||
|
DEFAULT_REFERENCE_SOURCE = "grb"
|
||||||
|
DEFAULT_REFERENCE_LAYER = "buildings"
|
||||||
REFERENCE_AOI_CATEGORY = "reference_aoi"
|
REFERENCE_AOI_CATEGORY = "reference_aoi"
|
||||||
PURE_EMPTY_BACKGROUND_CATEGORY = "pure_empty_negative"
|
PURE_EMPTY_BACKGROUND_CATEGORY = "pure_empty_negative"
|
||||||
SPARSE_BACKGROUND_CATEGORY = "sparse_building_context"
|
SPARSE_BACKGROUND_CATEGORY = "sparse_building_context"
|
||||||
@@ -74,6 +77,21 @@ def parse_args() -> argparse.Namespace:
|
|||||||
default=Path(os.environ.get("OPERATOR_YOLO_TILE_DATASET_DIR", DEFAULT_OUTPUT_DIR)),
|
default=Path(os.environ.get("OPERATOR_YOLO_TILE_DATASET_DIR", DEFAULT_OUTPUT_DIR)),
|
||||||
help="Output directory for tile images, labels, dataset.yaml and summary JSON.",
|
help="Output directory for tile images, labels, dataset.yaml and summary JSON.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--class-name",
|
||||||
|
default=os.environ.get("OPERATOR_YOLO_CLASS_NAME", DEFAULT_CLASS_NAME),
|
||||||
|
help="Canonical single detection class written to dataset.yaml.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--reference-source",
|
||||||
|
default=os.environ.get("OPERATOR_YOLO_REFERENCE_SOURCE", DEFAULT_REFERENCE_SOURCE),
|
||||||
|
help="Required source_name in reference GeoJSON features.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--reference-layer",
|
||||||
|
default=os.environ.get("OPERATOR_YOLO_REFERENCE_LAYER", DEFAULT_REFERENCE_LAYER),
|
||||||
|
help="Required reference_layer_name in reference GeoJSON features.",
|
||||||
|
)
|
||||||
parser.add_argument("--tile-size", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_SIZE", "256")))
|
parser.add_argument("--tile-size", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_SIZE", "256")))
|
||||||
parser.add_argument("--stride", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_STRIDE", "128")))
|
parser.add_argument("--stride", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_STRIDE", "128")))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -326,7 +344,14 @@ def iter_geometry_coords(geometry: dict[str, Any]) -> Iterable[tuple[float, floa
|
|||||||
yield float(point[0]), float(point[1])
|
yield float(point[0]), float(point[1])
|
||||||
|
|
||||||
|
|
||||||
def load_reference_pixel_boxes(reference_path: Path, dataset: Any, min_label_px: float) -> list[PixelBox]:
|
def load_reference_pixel_boxes(
|
||||||
|
reference_path: Path,
|
||||||
|
dataset: Any,
|
||||||
|
min_label_px: float,
|
||||||
|
*,
|
||||||
|
reference_source: str,
|
||||||
|
reference_layer: str,
|
||||||
|
) -> list[PixelBox]:
|
||||||
reference = json.loads(reference_path.read_text(encoding="utf-8-sig"))
|
reference = json.loads(reference_path.read_text(encoding="utf-8-sig"))
|
||||||
features = reference.get("features") or []
|
features = reference.get("features") or []
|
||||||
transformer = Transformer.from_crs("EPSG:4326", dataset.crs, always_xy=True)
|
transformer = Transformer.from_crs("EPSG:4326", dataset.crs, always_xy=True)
|
||||||
@@ -334,9 +359,9 @@ def load_reference_pixel_boxes(reference_path: Path, dataset: Any, min_label_px:
|
|||||||
|
|
||||||
for feature in features:
|
for feature in features:
|
||||||
properties = feature.get("properties") or {}
|
properties = feature.get("properties") or {}
|
||||||
if properties.get("source_name") != "grb":
|
if str(properties.get("source_name") or "").strip().lower() != reference_source:
|
||||||
continue
|
continue
|
||||||
if properties.get("reference_layer_name") != "buildings":
|
if str(properties.get("reference_layer_name") or "").strip().lower() != reference_layer:
|
||||||
continue
|
continue
|
||||||
coords = list(iter_geometry_coords(feature.get("geometry") or {}))
|
coords = list(iter_geometry_coords(feature.get("geometry") or {}))
|
||||||
if not coords:
|
if not coords:
|
||||||
@@ -423,7 +448,7 @@ def ensure_yolo_directories(output_dir: Path) -> None:
|
|||||||
(output_dir / relative_path).mkdir(parents=True, exist_ok=True)
|
(output_dir / relative_path).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def write_dataset_yaml(output_dir: Path) -> Path:
|
def write_dataset_yaml(output_dir: Path, class_name: str) -> Path:
|
||||||
yaml_path = output_dir / "dataset.yaml"
|
yaml_path = output_dir / "dataset.yaml"
|
||||||
yaml_path.write_text(
|
yaml_path.write_text(
|
||||||
"\n".join(
|
"\n".join(
|
||||||
@@ -432,7 +457,7 @@ def write_dataset_yaml(output_dir: Path) -> Path:
|
|||||||
"train: images/train",
|
"train: images/train",
|
||||||
"val: images/val",
|
"val: images/val",
|
||||||
"names:",
|
"names:",
|
||||||
" 0: building",
|
f" 0: {class_name}",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -454,6 +479,8 @@ def export_sample_tiles(
|
|||||||
background_negative_repeat: int,
|
background_negative_repeat: int,
|
||||||
drop_low_variance_negatives: bool,
|
drop_low_variance_negatives: bool,
|
||||||
blank_range_threshold: int,
|
blank_range_threshold: int,
|
||||||
|
reference_source: str,
|
||||||
|
reference_layer: str,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
sample_slug = str(sample["sample_slug"])
|
sample_slug = str(sample["sample_slug"])
|
||||||
sample_role = str(sample.get("sample_role") or "reference")
|
sample_role = str(sample.get("sample_role") or "reference")
|
||||||
@@ -469,7 +496,13 @@ def export_sample_tiles(
|
|||||||
|
|
||||||
exported: list[dict[str, Any]] = []
|
exported: list[dict[str, Any]] = []
|
||||||
with rasterio.open(raster_path) as dataset:
|
with rasterio.open(raster_path) as dataset:
|
||||||
boxes = load_reference_pixel_boxes(reference_path, dataset, min_label_px=min_label_px)
|
boxes = load_reference_pixel_boxes(
|
||||||
|
reference_path,
|
||||||
|
dataset,
|
||||||
|
min_label_px=min_label_px,
|
||||||
|
reference_source=reference_source,
|
||||||
|
reference_layer=reference_layer,
|
||||||
|
)
|
||||||
for tile_index, tile_window in enumerate(iter_tile_windows(dataset.width, dataset.height, tile_size, stride)):
|
for tile_index, tile_window in enumerate(iter_tile_windows(dataset.width, dataset.height, tile_size, stride)):
|
||||||
labels = labels_for_tile(
|
labels = labels_for_tile(
|
||||||
tile_window,
|
tile_window,
|
||||||
@@ -566,6 +599,16 @@ def export_sample_tiles(
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
class_name = args.class_name.strip().lower()
|
||||||
|
reference_source = args.reference_source.strip().lower()
|
||||||
|
reference_layer = args.reference_layer.strip().lower()
|
||||||
|
for label, value in (
|
||||||
|
("class-name", class_name),
|
||||||
|
("reference-source", reference_source),
|
||||||
|
("reference-layer", reference_layer),
|
||||||
|
):
|
||||||
|
if not value or any(character not in "abcdefghijklmnopqrstuvwxyz0123456789_-" for character in value):
|
||||||
|
raise SystemExit(f"YOLO {label} must be a non-empty canonical slug")
|
||||||
ensure_dependencies()
|
ensure_dependencies()
|
||||||
if args.force and args.output_dir.exists():
|
if args.force and args.output_dir.exists():
|
||||||
shutil.rmtree(args.output_dir)
|
shutil.rmtree(args.output_dir)
|
||||||
@@ -597,6 +640,8 @@ def main() -> int:
|
|||||||
background_negative_repeat=args.background_negative_repeat,
|
background_negative_repeat=args.background_negative_repeat,
|
||||||
drop_low_variance_negatives=args.drop_low_variance_negatives,
|
drop_low_variance_negatives=args.drop_low_variance_negatives,
|
||||||
blank_range_threshold=args.blank_range_threshold,
|
blank_range_threshold=args.blank_range_threshold,
|
||||||
|
reference_source=reference_source,
|
||||||
|
reference_layer=reference_layer,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -605,7 +650,7 @@ def main() -> int:
|
|||||||
raise SystemExit("YOLO tile dataset export produced no training tiles")
|
raise SystemExit("YOLO tile dataset export produced no training tiles")
|
||||||
if not any(tile["split"] == "val" for tile in kept_tiles):
|
if not any(tile["split"] == "val" for tile in kept_tiles):
|
||||||
raise SystemExit("YOLO tile dataset export produced no validation tiles")
|
raise SystemExit("YOLO tile dataset export produced no validation tiles")
|
||||||
dataset_yaml = write_dataset_yaml(args.output_dir)
|
dataset_yaml = write_dataset_yaml(args.output_dir, class_name)
|
||||||
positive_tiles = [tile for tile in kept_tiles if not tile["is_negative"]]
|
positive_tiles = [tile for tile in kept_tiles if not tile["is_negative"]]
|
||||||
negative_tiles = [tile for tile in kept_tiles if tile["is_negative"]]
|
negative_tiles = [tile for tile in kept_tiles if tile["is_negative"]]
|
||||||
skipped_negative_tiles = [tile for tile in exported_tiles if not tile["kept"] and tile["is_negative"]]
|
skipped_negative_tiles = [tile for tile in exported_tiles if not tile["kept"] and tile["is_negative"]]
|
||||||
@@ -619,7 +664,9 @@ def main() -> int:
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"dataset_yaml": str(dataset_yaml),
|
"dataset_yaml": str(dataset_yaml),
|
||||||
"output_dir": str(args.output_dir),
|
"output_dir": str(args.output_dir),
|
||||||
"class_names": ["building"],
|
"class_names": [class_name],
|
||||||
|
"reference_source": reference_source,
|
||||||
|
"reference_layer": reference_layer,
|
||||||
"tile_size": args.tile_size,
|
"tile_size": args.tile_size,
|
||||||
"stride": args.stride,
|
"stride": args.stride,
|
||||||
"negative_keep_ratio": args.negative_keep_ratio,
|
"negative_keep_ratio": args.negative_keep_ratio,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Train a local operator YOLO building detector from an exported GeoIntel dataset.
|
Train a local operator YOLO detector from an exported, governed GeoIntel dataset.
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
OPERATOR_YOLO_DATASET_DIR Directory containing dataset.yaml.
|
OPERATOR_YOLO_DATASET_DIR Directory containing dataset.yaml.
|
||||||
@@ -44,6 +44,7 @@ TRAIN_IMGSZ="${TRAIN_IMGSZ:-512}"
|
|||||||
TRAIN_BATCH="${TRAIN_BATCH:-2}"
|
TRAIN_BATCH="${TRAIN_BATCH:-2}"
|
||||||
TRAIN_WORKERS="${TRAIN_WORKERS:-0}"
|
TRAIN_WORKERS="${TRAIN_WORKERS:-0}"
|
||||||
TRAIN_DEVICE="${TRAIN_DEVICE:-cpu}"
|
TRAIN_DEVICE="${TRAIN_DEVICE:-cpu}"
|
||||||
|
TRAIN_REQUIRE_CUDA="${TRAIN_REQUIRE_CUDA:-false}"
|
||||||
if [[ -z "${PYTHON_BIN:-}" ]]; then
|
if [[ -z "${PYTHON_BIN:-}" ]]; then
|
||||||
if [[ -x "/opt/geointel/venv/bin/python" ]]; then
|
if [[ -x "/opt/geointel/venv/bin/python" ]]; then
|
||||||
PYTHON_BIN="/opt/geointel/venv/bin/python"
|
PYTHON_BIN="/opt/geointel/venv/bin/python"
|
||||||
@@ -64,6 +65,7 @@ export TRAIN_IMGSZ
|
|||||||
export TRAIN_BATCH
|
export TRAIN_BATCH
|
||||||
export TRAIN_WORKERS
|
export TRAIN_WORKERS
|
||||||
export TRAIN_DEVICE
|
export TRAIN_DEVICE
|
||||||
|
export TRAIN_REQUIRE_CUDA
|
||||||
export PYTHON_BIN
|
export PYTHON_BIN
|
||||||
export SUMMARY_PATH
|
export SUMMARY_PATH
|
||||||
|
|
||||||
@@ -116,6 +118,7 @@ def seed_ultralytics_font() -> None:
|
|||||||
seed_ultralytics_font()
|
seed_ultralytics_font()
|
||||||
|
|
||||||
from ultralytics import YOLO
|
from ultralytics import YOLO
|
||||||
|
import torch
|
||||||
|
|
||||||
dataset_yaml = Path(os.environ["DATASET_YAML"])
|
dataset_yaml = Path(os.environ["DATASET_YAML"])
|
||||||
base_model_path = Path(os.environ["YOLO_BASE_MODEL_PATH"])
|
base_model_path = Path(os.environ["YOLO_BASE_MODEL_PATH"])
|
||||||
@@ -128,6 +131,9 @@ image_size = int(os.environ["TRAIN_IMGSZ"])
|
|||||||
batch_size = int(os.environ["TRAIN_BATCH"])
|
batch_size = int(os.environ["TRAIN_BATCH"])
|
||||||
workers = int(os.environ["TRAIN_WORKERS"])
|
workers = int(os.environ["TRAIN_WORKERS"])
|
||||||
device = os.environ["TRAIN_DEVICE"]
|
device = os.environ["TRAIN_DEVICE"]
|
||||||
|
require_cuda = os.environ["TRAIN_REQUIRE_CUDA"].strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
if require_cuda and (not device.lower().startswith("cuda") or not torch.cuda.is_available()):
|
||||||
|
raise SystemExit("TRAIN_REQUIRE_CUDA is enabled but the requested CUDA device is unavailable")
|
||||||
|
|
||||||
model = YOLO(str(base_model_path))
|
model = YOLO(str(base_model_path))
|
||||||
model.train(
|
model.train(
|
||||||
@@ -179,6 +185,9 @@ summary = {
|
|||||||
"batch_size": batch_size,
|
"batch_size": batch_size,
|
||||||
"workers": workers,
|
"workers": workers,
|
||||||
"device": device,
|
"device": device,
|
||||||
|
"cuda_required": require_cuda,
|
||||||
|
"cuda_available": torch.cuda.is_available(),
|
||||||
|
"torch_version": torch.__version__,
|
||||||
}
|
}
|
||||||
summary_path.parent.mkdir(parents=True, exist_ok=True)
|
summary_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True), encoding="utf-8")
|
summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True), encoding="utf-8")
|
||||||
|
|||||||
Reference in New Issue
Block a user