Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r test_requirements.txt
- name: Full integration test
pip install pytest pyyaml optuna
pip install -e . --no-deps
- name: Unit tests
run: |
# `./component-library` was removed in the repo reorganization;
# run_tests.py is at the repo root.
python ./run_tests.py
pytest tests/unit/test_iterate2.py -v
27 changes: 26 additions & 1 deletion docs/iterate2.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,38 @@ The **last** occurrence of the pattern `<metric_name>: <value>` or `<metric_name
--metrics score_linear_acc,score_modality_leak,score_combined
```

All objectives are maximised. `iterate2` prints the Pareto-front trials at the end:
`iterate2` prints the Pareto-front trials at the end:

```
Trial 12: Values=[0.873, 0.041, 0.791]
Trial 17: Values=[0.901, 0.038, 0.812]
```

#### Metric directions (minimize / maximize)

By default the direction per metric is inferred from its name: any metric whose name contains `loss`, `error`, `err`, `mse`, `mae`, or `rmse` is **minimized**; all others are **maximized**.

To override the direction explicitly, use the extended form in the `metrics:` section of the HPO YAML:

```yaml
metrics:
- name: val_loss # minimize (also inferred automatically)
direction: minimize
- name: accuracy # maximize (also inferred automatically)
direction: maximize
- name: f1_score # maximize (explicit override)
direction: maximize
```

A mix of simple strings and extended objects is allowed:

```yaml
metrics:
- val_loss # direction inferred → minimize
- name: my_custom_score
direction: maximize # direction explicit
```

---

## Setter-style arguments
Expand Down
81 changes: 69 additions & 12 deletions terratorch_iterate/iterate2/_iterate2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@
HPO YAML format
---------------
metrics: # list of metric names to extract from ITERATE_OUT_FILE
- val_loss
- accuracy
- val_loss # simple form – direction defaults to "minimize" when
- accuracy # the name contains "loss" or "error", else "maximize"

# Extended form: specify direction explicitly per metric
metrics:
- name: val_loss
direction: minimize
- name: accuracy
direction: maximize

static: # fixed parameters, forwarded as-is every trial
epochs: 50
Expand Down Expand Up @@ -97,14 +104,64 @@ def load_static(data: dict) -> dict:
logger.info("Static params: %d key(s): %s", len(static), list(static.keys()))
return static

def load_metrics(data: dict, fallback: str = "score") -> List[str]:
_MINIMIZE_KEYWORDS = ("loss", "error", "err", "mse", "mae", "rmse")

def _default_direction(metric_name: str) -> str:
"""Heuristic: metrics whose name contains a loss/error keyword are minimized."""
lower = metric_name.lower()
if any(kw in lower for kw in _MINIMIZE_KEYWORDS):
return "minimize"
return "maximize"

def load_metrics(data: dict, fallback: str = "score") -> tuple[List[str], List[str]]:
"""Return (metric_names, directions).

The YAML ``metrics:`` section accepts two forms:

Simple (direction inferred from name)::

metrics:
- val_loss # → minimize (contains "loss")
- accuracy # → maximize

Extended (direction explicit)::

metrics:
- name: val_loss
direction: minimize
- name: accuracy
direction: maximize

A mix of both forms is allowed.
"""
raw = data.get("metrics", None)
if raw is None:
logger.warning("No 'metrics:' key in YAML – defaulting to '%s'", fallback)
return [fallback]
if isinstance(raw, list):
return [str(m).strip() for m in raw]
return [m.strip() for m in str(raw).split(",")]
return [fallback], [_default_direction(fallback)]

if not isinstance(raw, list):
# comma-separated string fallback
names = [m.strip() for m in str(raw).split(",")]
directions = [_default_direction(n) for n in names]
return names, directions

names: List[str] = []
directions: List[str] = []
for item in raw:
if isinstance(item, dict):
name = str(item["name"]).strip()
direction = str(item.get("direction", _default_direction(name))).strip().lower()
if direction not in ("minimize", "maximize"):
raise ValueError(
f"Invalid direction '{direction}' for metric '{name}'. "
"Must be 'minimize' or 'maximize'."
)
else:
name = str(item).strip()
direction = _default_direction(name)
names.append(name)
directions.append(direction)
return names, directions


# ─── OPTUNA PARAM SAMPLING ───────────────────────────────────────────────────
Expand Down Expand Up @@ -216,12 +273,12 @@ def main():
args.optuna_study_name, args.optuna_db_path,
args.optuna_n_trials, args.parallelism)

data = load_yaml(args.hpo_yaml)
hpo_space = load_hpo_space(data)
static = load_static(data)
metrics = load_metrics(data)
directions = ["maximize"] * len(metrics)
data = load_yaml(args.hpo_yaml)
hpo_space = load_hpo_space(data)
static = load_static(data)
metrics, directions = load_metrics(data)
logger.info("Metrics: %s", metrics)
logger.info("Directions: %s", directions)

storage = resolve_storage(args.optuna_db_path)
study = optuna.create_study(
Expand Down
Loading