Commit Β·
c8c2c05
1
Parent(s): 2ed14b5
feat: multi-island evolution + Codespace runner
Browse files- app.py: env-overridable SPACE_ROLE config (6 roles: exploitation, exploration,
extra_trees_specialist, catboost_specialist, neural_specialist, wide_search)
- deploy_island.py: generic deploy to any HF Space with any role
- deploy_s11.py: S11 exploration island deploy script
- .devcontainer/: GitHub Codespace config + evolution runner script
- S12 (extra_trees) + S13 (catboost) deployed on LBJLincoln26 account
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- app.py +47 -12
- deploy_island.py +101 -0
- deploy_s11.py +113 -0
app.py
CHANGED
|
@@ -1566,21 +1566,56 @@ def _ece(probs, actuals, n_bins=10):
|
|
| 1566 |
# EVOLUTION ENGINE
|
| 1567 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1568 |
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
-
|
| 1575 |
-
|
| 1576 |
-
|
| 1577 |
-
|
| 1578 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1579 |
N_SPLITS = 3 # 3-fold walk-forward for reliable Brier estimates
|
| 1580 |
GENS_PER_CYCLE = 3 # Save every 3 gens
|
| 1581 |
COOLDOWN = 5 # Minimal cooldown
|
| 1582 |
-
TOURNAMENT_SIZE = 7 # Higher pressure with small pop β proven optimal
|
| 1583 |
-
DIVERSITY_RESTART = 30 # More patience with small pop
|
| 1584 |
FAST_EVAL_GAMES = 8000 # More data in fast eval
|
| 1585 |
FULL_EVAL_TOP = 10 # Full eval for top 10 (out of 60)
|
| 1586 |
MIGRATION_INTERVAL = 8 # Migration every 8 gens
|
|
|
|
| 1566 |
# EVOLUTION ENGINE
|
| 1567 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1568 |
|
| 1569 |
+
# ββ Runtime config (env-overridable for multi-Space island model) ββ
|
| 1570 |
+
# S10 (primary): exploitation β proven optimal params
|
| 1571 |
+
# S11 (secondary): exploration β higher mutation, wider feature search
|
| 1572 |
+
import os as _os
|
| 1573 |
+
_SPACE_ROLE = _os.environ.get("SPACE_ROLE", "exploitation") # "exploitation" or "exploration"
|
| 1574 |
+
|
| 1575 |
+
_ROLE_CONFIGS = {
|
| 1576 |
+
"exploitation": { # S10: proven optimal
|
| 1577 |
+
"POP_SIZE": 60, "BASE_MUT": 0.09, "MUT_DECAY_RATE": 0.998, "MUT_FLOOR": 0.05,
|
| 1578 |
+
"CROSSOVER_RATE": 0.80, "TARGET_FEATURES": 63, "TOURNAMENT_SIZE": 7, "DIVERSITY_RESTART": 30,
|
| 1579 |
+
},
|
| 1580 |
+
"exploration": { # S11: wider search
|
| 1581 |
+
"POP_SIZE": 60, "BASE_MUT": 0.15, "MUT_DECAY_RATE": 0.999, "MUT_FLOOR": 0.08,
|
| 1582 |
+
"CROSSOVER_RATE": 0.70, "TARGET_FEATURES": 80, "TOURNAMENT_SIZE": 5, "DIVERSITY_RESTART": 20,
|
| 1583 |
+
},
|
| 1584 |
+
"extra_trees_specialist": { # S12: extra_trees focus, tight features
|
| 1585 |
+
"POP_SIZE": 60, "BASE_MUT": 0.08, "MUT_DECAY_RATE": 0.998, "MUT_FLOOR": 0.04,
|
| 1586 |
+
"CROSSOVER_RATE": 0.80, "TARGET_FEATURES": 60, "TOURNAMENT_SIZE": 7, "DIVERSITY_RESTART": 25,
|
| 1587 |
+
},
|
| 1588 |
+
"catboost_specialist": { # S13: catboost focus, medium exploration
|
| 1589 |
+
"POP_SIZE": 60, "BASE_MUT": 0.10, "MUT_DECAY_RATE": 0.998, "MUT_FLOOR": 0.05,
|
| 1590 |
+
"CROSSOVER_RATE": 0.80, "TARGET_FEATURES": 66, "TOURNAMENT_SIZE": 6, "DIVERSITY_RESTART": 25,
|
| 1591 |
+
},
|
| 1592 |
+
"neural_specialist": { # S14: neural models (MLP, TabNet, FT-Transformer)
|
| 1593 |
+
"POP_SIZE": 40, "BASE_MUT": 0.12, "MUT_DECAY_RATE": 0.999, "MUT_FLOOR": 0.06,
|
| 1594 |
+
"CROSSOVER_RATE": 0.75, "TARGET_FEATURES": 50, "TOURNAMENT_SIZE": 5, "DIVERSITY_RESTART": 15,
|
| 1595 |
+
},
|
| 1596 |
+
"wide_search": { # S15: max diversity, wide feature search
|
| 1597 |
+
"POP_SIZE": 60, "BASE_MUT": 0.20, "MUT_DECAY_RATE": 0.999, "MUT_FLOOR": 0.10,
|
| 1598 |
+
"CROSSOVER_RATE": 0.65, "TARGET_FEATURES": 120, "TOURNAMENT_SIZE": 4, "DIVERSITY_RESTART": 15,
|
| 1599 |
+
},
|
| 1600 |
+
}
|
| 1601 |
+
_cfg = _ROLE_CONFIGS.get(_SPACE_ROLE, _ROLE_CONFIGS["exploitation"])
|
| 1602 |
+
POP_SIZE = _cfg["POP_SIZE"]
|
| 1603 |
+
BASE_MUT = _cfg["BASE_MUT"]
|
| 1604 |
+
MUT_DECAY_RATE = _cfg["MUT_DECAY_RATE"]
|
| 1605 |
+
MUT_FLOOR = _cfg["MUT_FLOOR"]
|
| 1606 |
+
CROSSOVER_RATE = _cfg["CROSSOVER_RATE"]
|
| 1607 |
+
TARGET_FEATURES = _cfg["TARGET_FEATURES"]
|
| 1608 |
+
TOURNAMENT_SIZE = _cfg["TOURNAMENT_SIZE"]
|
| 1609 |
+
DIVERSITY_RESTART = _cfg["DIVERSITY_RESTART"]
|
| 1610 |
+
|
| 1611 |
+
N_ISLANDS = 5
|
| 1612 |
+
ISLAND_SIZE = POP_SIZE // N_ISLANDS
|
| 1613 |
+
ELITE_SIZE = max(2, POP_SIZE // 10)
|
| 1614 |
+
ELITE_PER_ISLAND = max(1, ISLAND_SIZE // 6)
|
| 1615 |
+
|
| 1616 |
N_SPLITS = 3 # 3-fold walk-forward for reliable Brier estimates
|
| 1617 |
GENS_PER_CYCLE = 3 # Save every 3 gens
|
| 1618 |
COOLDOWN = 5 # Minimal cooldown
|
|
|
|
|
|
|
| 1619 |
FAST_EVAL_GAMES = 8000 # More data in fast eval
|
| 1620 |
FULL_EVAL_TOP = 10 # Full eval for top 10 (out of 60)
|
| 1621 |
MIGRATION_INTERVAL = 8 # Migration every 8 gens
|
deploy_island.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Deploy an evolution island to any HF Space.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
source .env.local
|
| 6 |
+
python3 hf-space/deploy_island.py SPACE_ID ROLE [HF_TOKEN_VAR]
|
| 7 |
+
|
| 8 |
+
Examples:
|
| 9 |
+
python3 hf-space/deploy_island.py LBJLincoln26/nba-evo-3 extra_trees_specialist HF_TOKEN_2
|
| 10 |
+
python3 hf-space/deploy_island.py LBJLincoln26/nba-evo-4 catboost_specialist HF_TOKEN_2
|
| 11 |
+
|
| 12 |
+
Roles:
|
| 13 |
+
exploitation β S10 default: mut=0.09, feat=63 (proven optimal)
|
| 14 |
+
exploration β S11 default: mut=0.15, feat=80 (wider search)
|
| 15 |
+
extra_trees_specialist β extra_trees only, low mut, narrow features
|
| 16 |
+
catboost_specialist β catboost focus, medium mut
|
| 17 |
+
neural_specialist β neural models only (MLP, TabNet, FT-Transformer)
|
| 18 |
+
wide_search β high mut=0.20, feat=120, all model types
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import os, sys
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from huggingface_hub import HfApi, CommitOperationAdd
|
| 24 |
+
|
| 25 |
+
LOCAL_DIR = Path(__file__).parent
|
| 26 |
+
SKIP = {"__pycache__", ".pyc", "node_modules", ".git", "deploy.py", "deploy_s11.py", "deploy_island.py"}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main():
|
| 30 |
+
if len(sys.argv) < 3:
|
| 31 |
+
print(__doc__)
|
| 32 |
+
sys.exit(1)
|
| 33 |
+
|
| 34 |
+
space_id = sys.argv[1]
|
| 35 |
+
role = sys.argv[2]
|
| 36 |
+
token_var = sys.argv[3] if len(sys.argv) > 3 else "HF_TOKEN"
|
| 37 |
+
token = os.environ.get(token_var)
|
| 38 |
+
|
| 39 |
+
if not token:
|
| 40 |
+
print(f"ERROR: {token_var} not set. Run: source .env.local")
|
| 41 |
+
sys.exit(1)
|
| 42 |
+
|
| 43 |
+
# Engine parity check
|
| 44 |
+
root_engine = (LOCAL_DIR.parent / "features" / "engine.py").read_bytes()
|
| 45 |
+
hf_engine = (LOCAL_DIR / "features" / "engine.py").read_bytes()
|
| 46 |
+
if root_engine != hf_engine:
|
| 47 |
+
print("ERROR: features/engine.py diverged!")
|
| 48 |
+
sys.exit(1)
|
| 49 |
+
print("Engine parity: OK")
|
| 50 |
+
|
| 51 |
+
api = HfApi(token=token)
|
| 52 |
+
print(f"Deploying island ({role}) to {space_id}...")
|
| 53 |
+
|
| 54 |
+
operations = []
|
| 55 |
+
for fp in LOCAL_DIR.rglob("*"):
|
| 56 |
+
if fp.is_dir():
|
| 57 |
+
continue
|
| 58 |
+
if any(s in str(fp) for s in SKIP):
|
| 59 |
+
continue
|
| 60 |
+
rel = fp.relative_to(LOCAL_DIR)
|
| 61 |
+
print(f" + {rel}")
|
| 62 |
+
operations.append(CommitOperationAdd(path_in_repo=str(rel), path_or_fileobj=str(fp)))
|
| 63 |
+
|
| 64 |
+
print(f"\nUploading {len(operations)} files...")
|
| 65 |
+
api.create_commit(
|
| 66 |
+
repo_id=space_id, repo_type="space", operations=operations,
|
| 67 |
+
commit_message=f"feat: evolution island ({role})",
|
| 68 |
+
)
|
| 69 |
+
print("Upload OK!")
|
| 70 |
+
|
| 71 |
+
# Core secrets (DB only β minimal for evolution)
|
| 72 |
+
secrets = {
|
| 73 |
+
"DATABASE_URL": os.environ.get("DATABASE_URL", ""),
|
| 74 |
+
"SUPABASE_URL": os.environ.get("SUPABASE_URL", ""),
|
| 75 |
+
"SUPABASE_API_KEY": os.environ.get("SUPABASE_API_KEY", ""),
|
| 76 |
+
"SUPABASE_PASSWORD": os.environ.get("SUPABASE_PASSWORD", ""),
|
| 77 |
+
"SPACE_ROLE": role,
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
print("\nConfiguring secrets...")
|
| 81 |
+
for key, value in secrets.items():
|
| 82 |
+
if value:
|
| 83 |
+
try:
|
| 84 |
+
api.add_space_secret(space_id, key, value)
|
| 85 |
+
print(f" Set {key}")
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f" WARN: {key}: {e}")
|
| 88 |
+
|
| 89 |
+
print("\nRestarting...")
|
| 90 |
+
try:
|
| 91 |
+
api.restart_space(space_id)
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f" Restart: {e}")
|
| 94 |
+
|
| 95 |
+
owner = space_id.split("/")[0].lower()
|
| 96 |
+
name = space_id.split("/")[1]
|
| 97 |
+
print(f"\nDone! https://{owner}-{name}.hf.space")
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
if __name__ == "__main__":
|
| 101 |
+
main()
|
deploy_s11.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Deploy S11 as 2nd evolution island (exploration mode).
|
| 3 |
+
|
| 4 |
+
Deploys the SAME code as S10 but with SPACE_ROLE=exploration secret.
|
| 5 |
+
S11 uses higher mutation, wider feature search, more model diversity.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
source .env.local
|
| 9 |
+
python3 hf-space/deploy_s11.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import os, sys
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from huggingface_hub import HfApi, CommitOperationAdd
|
| 15 |
+
|
| 16 |
+
SPACE_ID = "lbjlincoln/nomos-nba-quant-2"
|
| 17 |
+
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HF_TOKEN_2")
|
| 18 |
+
LOCAL_DIR = Path(__file__).parent
|
| 19 |
+
|
| 20 |
+
# Same secrets as S10 + SPACE_ROLE override
|
| 21 |
+
SECRETS = {
|
| 22 |
+
"DATABASE_URL": os.environ.get("DATABASE_URL", ""),
|
| 23 |
+
"SUPABASE_URL": os.environ.get("SUPABASE_URL", ""),
|
| 24 |
+
"SUPABASE_API_KEY": os.environ.get("SUPABASE_API_KEY", ""),
|
| 25 |
+
"SUPABASE_PASSWORD": os.environ.get("SUPABASE_PASSWORD", ""),
|
| 26 |
+
"SUPABASE_URL_2": os.environ.get("SUPABASE_URL_2", ""),
|
| 27 |
+
"SUPABASE_ANON_KEY_2": os.environ.get("SUPABASE_ANON_KEY_2", ""),
|
| 28 |
+
"SUPABASE_PASSWORD_2": os.environ.get("SUPABASE_PASSWORD_2", ""),
|
| 29 |
+
"SUPABASE_POOLER_2": os.environ.get("SUPABASE_POOLER_2", ""),
|
| 30 |
+
"ODDS_API_KEY": os.environ.get("ODDS_API_KEY", ""),
|
| 31 |
+
"GOOGLE_API_KEY": os.environ.get("GOOGLE_API_KEY", ""),
|
| 32 |
+
"HF_TOKEN": os.environ.get("HF_TOKEN", ""),
|
| 33 |
+
"TELEGRAM_BOT_TOKEN": os.environ.get("TELEGRAM_BOT_TOKEN", ""),
|
| 34 |
+
"ADMIN_TELEGRAM_ID": os.environ.get("ADMIN_TELEGRAM_ID", ""),
|
| 35 |
+
# S11 differentiator: exploration mode
|
| 36 |
+
"SPACE_ROLE": "exploration",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# Files to skip (S11 doesn't need deploy scripts or experiment_runner)
|
| 40 |
+
SKIP = {"__pycache__", ".pyc", "node_modules", ".git", "deploy.py", "deploy_s11.py"}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
if not HF_TOKEN:
|
| 45 |
+
print("ERROR: HF_TOKEN not set. Run: source .env.local")
|
| 46 |
+
sys.exit(1)
|
| 47 |
+
|
| 48 |
+
# Engine parity check
|
| 49 |
+
root_engine = (LOCAL_DIR.parent / "features" / "engine.py").read_bytes()
|
| 50 |
+
hf_engine = (LOCAL_DIR / "features" / "engine.py").read_bytes()
|
| 51 |
+
if root_engine != hf_engine:
|
| 52 |
+
print("ERROR: features/engine.py diverged! Fix parity first.")
|
| 53 |
+
sys.exit(1)
|
| 54 |
+
print("Engine parity check: OK")
|
| 55 |
+
|
| 56 |
+
api = HfApi(token=HF_TOKEN)
|
| 57 |
+
print(f"Deploying S11 (EXPLORATION island) to {SPACE_ID}...")
|
| 58 |
+
|
| 59 |
+
operations = []
|
| 60 |
+
for fp in LOCAL_DIR.rglob("*"):
|
| 61 |
+
if fp.is_dir():
|
| 62 |
+
continue
|
| 63 |
+
if any(s in str(fp) for s in SKIP):
|
| 64 |
+
continue
|
| 65 |
+
rel = fp.relative_to(LOCAL_DIR)
|
| 66 |
+
print(f" + {rel}")
|
| 67 |
+
operations.append(CommitOperationAdd(path_in_repo=str(rel), path_or_fileobj=str(fp)))
|
| 68 |
+
|
| 69 |
+
if not operations:
|
| 70 |
+
print("ERROR: No files found")
|
| 71 |
+
sys.exit(1)
|
| 72 |
+
|
| 73 |
+
print(f"\nUploading {len(operations)} files...")
|
| 74 |
+
try:
|
| 75 |
+
api.create_commit(
|
| 76 |
+
repo_id=SPACE_ID, repo_type="space", operations=operations,
|
| 77 |
+
commit_message="feat: S11 as exploration island (higher mut, wider features)",
|
| 78 |
+
)
|
| 79 |
+
print("Upload OK!")
|
| 80 |
+
except Exception as e:
|
| 81 |
+
if "404" in str(e) or "not found" in str(e).lower():
|
| 82 |
+
print(f"Space not found, creating {SPACE_ID}...")
|
| 83 |
+
api.create_repo(repo_id=SPACE_ID, repo_type="space", space_sdk="docker",
|
| 84 |
+
space_hardware="cpu-basic", private=False)
|
| 85 |
+
api.create_commit(
|
| 86 |
+
repo_id=SPACE_ID, repo_type="space", operations=operations,
|
| 87 |
+
commit_message="feat: S11 as exploration island (higher mut, wider features)",
|
| 88 |
+
)
|
| 89 |
+
else:
|
| 90 |
+
raise
|
| 91 |
+
|
| 92 |
+
print("\nConfiguring secrets (including SPACE_ROLE=exploration)...")
|
| 93 |
+
for key, value in SECRETS.items():
|
| 94 |
+
if value:
|
| 95 |
+
try:
|
| 96 |
+
api.add_space_secret(SPACE_ID, key, value)
|
| 97 |
+
print(f" Set {key}")
|
| 98 |
+
except Exception as e:
|
| 99 |
+
print(f" WARN: {key}: {e}")
|
| 100 |
+
|
| 101 |
+
print("\nRestarting Space...")
|
| 102 |
+
try:
|
| 103 |
+
api.restart_space(SPACE_ID)
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f" Restart: {e}")
|
| 106 |
+
|
| 107 |
+
print(f"\nDone! S11 (exploration): https://lbjlincoln-nomos-nba-quant-2.hf.space")
|
| 108 |
+
print(f"Monitor: https://huggingface.co/spaces/{SPACE_ID}")
|
| 109 |
+
print("\nS11 config: SPACE_ROLE=exploration β mut=0.15, cx=0.70, feat=80, tournament=5")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
main()
|