-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun.py
More file actions
179 lines (153 loc) · 6.2 KB
/
Copy pathrun.py
File metadata and controls
179 lines (153 loc) · 6.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env python3
"""Run EcoNetToolkit: python run.py --config configs/example_config.yaml"""
import argparse
import numpy as np
import os
import sys
from ecosci.config import load_config
from ecosci.data import CSVDataLoader
from ecosci.models import ModelZoo
from ecosci.trainer import Trainer
from ecosci.evaluation import evaluate_and_report, evaluate_and_report_cv
parser = argparse.ArgumentParser(description="Train a model using a YAML config.")
parser.add_argument("--config", required=True, help="Path to YAML config file")
args = parser.parse_args()
cfg = load_config(args.config)
# Create output directory based on config name if not specified
if "dir" not in cfg.get("output", {}):
config_name = os.path.splitext(os.path.basename(args.config))[0]
cfg.setdefault("output", {})
cfg["output"]["dir"] = os.path.join("outputs", config_name)
# Load and prepare data
data_cfg = cfg.get("data", {})
problem_type = cfg.get("problem_type", "classification")
cv_group_column = data_cfg.get("cv_group_column")
loader = CSVDataLoader(
path=data_cfg.get("path"),
features=data_cfg.get("features"),
label=data_cfg.get("label"),
labels=data_cfg.get("labels"),
test_size=data_cfg.get("test_size", 0.2),
val_size=data_cfg.get("val_size", 0.2),
random_state=data_cfg.get("random_state", 0),
scaling=data_cfg.get("scaling", "standard"),
impute_strategy=data_cfg.get("impute_strategy", "mean"),
problem_type=problem_type,
cv_group_column=cv_group_column,
spatial_blocks=data_cfg.get("spatial_blocks"),
)
# Get output directory
output_dir = cfg.get("output", {}).get("dir", "outputs")
# Mirror console output to a log file in the output directory
os.makedirs(output_dir, exist_ok=True)
class _Tee:
def __init__(self, *streams):
self.streams = streams
def write(self, data):
for stream in self.streams:
stream.write(data)
def flush(self):
for stream in self.streams:
stream.flush()
log_file = open(os.path.join(output_dir, "run.log"), "w")
sys.stdout = _Tee(sys.stdout, log_file)
# Train
trainer = Trainer(
ModelZoo.get_model,
problem_type=cfg.get("problem_type", "classification"),
output_dir=output_dir,
)
# Check if hyperparameter tuning is enabled
tuning_enabled = cfg.get("tuning", {}).get("enabled", False)
# Determine which mode to use
if tuning_enabled and cv_group_column is not None:
# Hyperparameter tuning mode with grouped train/val/test splits
n_train_groups = data_cfg.get("n_train_groups", 4)
n_val_groups = data_cfg.get("n_val_groups", 2)
n_test_groups = data_cfg.get("n_test_groups", 2)
print(f"\n{'='*70}")
print(f"Hyperparameter Tuning Mode")
print(f"{'='*70}")
print(f"Using grouped train/val/test splits with group column: {cv_group_column}")
print(f" Train groups: {n_train_groups}")
print(f" Val groups: {n_val_groups}")
print(f" Test groups: {n_test_groups}")
print(f"{'='*70}\n")
# Prepare grouped splits
(X_train, X_val, X_test, y_train, y_val, y_test, group_assignments,
groups_train, groups_val, groups_test) = \
loader.prepare_grouped_splits(n_train_groups, n_val_groups, n_test_groups)
# Run training with hyperparameter tuning
results = trainer.run_with_tuning(
cfg, X_train, X_val, X_test, y_train, y_val, y_test, group_assignments,
groups_train, groups_val
)
# Evaluate on both validation and test sets
# For tuning mode, we want to see performance on both val and test
from ecosci.evaluation import evaluate_tuning_results
summary = evaluate_tuning_results(
results,
y_val,
y_test,
output_dir=output_dir,
problem_type=problem_type,
label_names=loader.labels if hasattr(loader, 'labels') else None,
feature_names=loader.processed_feature_names if hasattr(loader, 'processed_feature_names') else None,
X_val=X_val,
X_test=X_test,
)
elif cv_group_column is not None:
# K-fold cross-validation mode (no tuning)
print(f"Running k-fold cross-validation using group column: {cv_group_column}")
fold_data_list = loader.prepare_cv_folds()
results = trainer.run_cv(cfg, fold_data_list)
# Evaluate with CV-specific reporting
summary = evaluate_and_report_cv(
results,
output_dir=output_dir,
problem_type=problem_type,
label_names=loader.labels if hasattr(loader, 'labels') else None,
feature_names=loader.processed_feature_names if hasattr(loader, 'processed_feature_names') else None,
)
else:
# Regular train/test split (no tuning, no CV)
X_train, X_val, X_test, y_train, y_val, y_test = loader.prepare()
results = trainer.run(cfg, X_train, X_val, X_test, y_train, y_val, y_test)
# Evaluate
summary = evaluate_and_report(
results,
y_test,
output_dir=output_dir,
problem_type=problem_type,
label_names=loader.labels if hasattr(loader, 'labels') else None,
feature_names=loader.processed_feature_names if hasattr(loader, 'processed_feature_names') else None,
X_test=X_test,
)
# Print quick summary
if tuning_enabled and cv_group_column is not None:
# For tuning mode
print(f"\n{'='*70}")
print(f"Hyperparameter Tuning Complete!")
print(f"{'='*70}")
print(f"Results saved to: {output_dir}/")
print(f" - Best hyperparameters per seed")
print(f" - Validation and test set predictions")
print(f" - Model checkpoints")
print(f"{'='*70}\n")
elif cv_group_column is not None:
# For CV, summary is a dict of DataFrames
print(f"\nDone. See {output_dir}/ for full CV reports and plots.")
else:
# For regular split, summary is a list of dicts
if problem_type == "regression":
r2s = [r.get("r2") for r in summary if "r2" in r]
if r2s:
print(f"\nMean R²: {np.mean(r2s):.3f}")
rmses = [r.get("rmse") for r in summary if "rmse" in r]
if rmses:
print(f"Mean RMSE: {np.mean(rmses):.3f}")
else:
accs = [r.get("accuracy") for r in summary if "accuracy" in r]
if accs:
print(f"\nMean accuracy: {np.mean(accs):.3f}")
print(f"Done. See {output_dir}/ for full report and plots.")