ins-pricing 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ins_pricing/README.md +60 -0
- ins_pricing/__init__.py +102 -0
- ins_pricing/governance/README.md +18 -0
- ins_pricing/governance/__init__.py +20 -0
- ins_pricing/governance/approval.py +93 -0
- ins_pricing/governance/audit.py +37 -0
- ins_pricing/governance/registry.py +99 -0
- ins_pricing/governance/release.py +159 -0
- ins_pricing/modelling/BayesOpt.py +146 -0
- ins_pricing/modelling/BayesOpt_USAGE.md +925 -0
- ins_pricing/modelling/BayesOpt_entry.py +575 -0
- ins_pricing/modelling/BayesOpt_incremental.py +731 -0
- ins_pricing/modelling/Explain_Run.py +36 -0
- ins_pricing/modelling/Explain_entry.py +539 -0
- ins_pricing/modelling/Pricing_Run.py +36 -0
- ins_pricing/modelling/README.md +33 -0
- ins_pricing/modelling/__init__.py +44 -0
- ins_pricing/modelling/bayesopt/__init__.py +98 -0
- ins_pricing/modelling/bayesopt/config_preprocess.py +303 -0
- ins_pricing/modelling/bayesopt/core.py +1476 -0
- ins_pricing/modelling/bayesopt/models.py +2196 -0
- ins_pricing/modelling/bayesopt/trainers.py +2446 -0
- ins_pricing/modelling/bayesopt/utils.py +1021 -0
- ins_pricing/modelling/cli_common.py +136 -0
- ins_pricing/modelling/explain/__init__.py +55 -0
- ins_pricing/modelling/explain/gradients.py +334 -0
- ins_pricing/modelling/explain/metrics.py +176 -0
- ins_pricing/modelling/explain/permutation.py +155 -0
- ins_pricing/modelling/explain/shap_utils.py +146 -0
- ins_pricing/modelling/notebook_utils.py +284 -0
- ins_pricing/modelling/plotting/__init__.py +45 -0
- ins_pricing/modelling/plotting/common.py +63 -0
- ins_pricing/modelling/plotting/curves.py +572 -0
- ins_pricing/modelling/plotting/diagnostics.py +139 -0
- ins_pricing/modelling/plotting/geo.py +362 -0
- ins_pricing/modelling/plotting/importance.py +121 -0
- ins_pricing/modelling/run_logging.py +133 -0
- ins_pricing/modelling/tests/conftest.py +8 -0
- ins_pricing/modelling/tests/test_cross_val_generic.py +66 -0
- ins_pricing/modelling/tests/test_distributed_utils.py +18 -0
- ins_pricing/modelling/tests/test_explain.py +56 -0
- ins_pricing/modelling/tests/test_geo_tokens_split.py +49 -0
- ins_pricing/modelling/tests/test_graph_cache.py +33 -0
- ins_pricing/modelling/tests/test_plotting.py +63 -0
- ins_pricing/modelling/tests/test_plotting_library.py +150 -0
- ins_pricing/modelling/tests/test_preprocessor.py +48 -0
- ins_pricing/modelling/watchdog_run.py +211 -0
- ins_pricing/pricing/README.md +44 -0
- ins_pricing/pricing/__init__.py +27 -0
- ins_pricing/pricing/calibration.py +39 -0
- ins_pricing/pricing/data_quality.py +117 -0
- ins_pricing/pricing/exposure.py +85 -0
- ins_pricing/pricing/factors.py +91 -0
- ins_pricing/pricing/monitoring.py +99 -0
- ins_pricing/pricing/rate_table.py +78 -0
- ins_pricing/production/__init__.py +21 -0
- ins_pricing/production/drift.py +30 -0
- ins_pricing/production/monitoring.py +143 -0
- ins_pricing/production/scoring.py +40 -0
- ins_pricing/reporting/README.md +20 -0
- ins_pricing/reporting/__init__.py +11 -0
- ins_pricing/reporting/report_builder.py +72 -0
- ins_pricing/reporting/scheduler.py +45 -0
- ins_pricing/setup.py +41 -0
- ins_pricing v2/__init__.py +23 -0
- ins_pricing v2/governance/__init__.py +20 -0
- ins_pricing v2/governance/approval.py +93 -0
- ins_pricing v2/governance/audit.py +37 -0
- ins_pricing v2/governance/registry.py +99 -0
- ins_pricing v2/governance/release.py +159 -0
- ins_pricing v2/modelling/Explain_Run.py +36 -0
- ins_pricing v2/modelling/Pricing_Run.py +36 -0
- ins_pricing v2/modelling/__init__.py +151 -0
- ins_pricing v2/modelling/cli_common.py +141 -0
- ins_pricing v2/modelling/config.py +249 -0
- ins_pricing v2/modelling/config_preprocess.py +254 -0
- ins_pricing v2/modelling/core.py +741 -0
- ins_pricing v2/modelling/data_container.py +42 -0
- ins_pricing v2/modelling/explain/__init__.py +55 -0
- ins_pricing v2/modelling/explain/gradients.py +334 -0
- ins_pricing v2/modelling/explain/metrics.py +176 -0
- ins_pricing v2/modelling/explain/permutation.py +155 -0
- ins_pricing v2/modelling/explain/shap_utils.py +146 -0
- ins_pricing v2/modelling/features.py +215 -0
- ins_pricing v2/modelling/model_manager.py +148 -0
- ins_pricing v2/modelling/model_plotting.py +463 -0
- ins_pricing v2/modelling/models.py +2203 -0
- ins_pricing v2/modelling/notebook_utils.py +294 -0
- ins_pricing v2/modelling/plotting/__init__.py +45 -0
- ins_pricing v2/modelling/plotting/common.py +63 -0
- ins_pricing v2/modelling/plotting/curves.py +572 -0
- ins_pricing v2/modelling/plotting/diagnostics.py +139 -0
- ins_pricing v2/modelling/plotting/geo.py +362 -0
- ins_pricing v2/modelling/plotting/importance.py +121 -0
- ins_pricing v2/modelling/run_logging.py +133 -0
- ins_pricing v2/modelling/tests/conftest.py +8 -0
- ins_pricing v2/modelling/tests/test_cross_val_generic.py +66 -0
- ins_pricing v2/modelling/tests/test_distributed_utils.py +18 -0
- ins_pricing v2/modelling/tests/test_explain.py +56 -0
- ins_pricing v2/modelling/tests/test_geo_tokens_split.py +49 -0
- ins_pricing v2/modelling/tests/test_graph_cache.py +33 -0
- ins_pricing v2/modelling/tests/test_plotting.py +63 -0
- ins_pricing v2/modelling/tests/test_plotting_library.py +150 -0
- ins_pricing v2/modelling/tests/test_preprocessor.py +48 -0
- ins_pricing v2/modelling/trainers.py +2447 -0
- ins_pricing v2/modelling/utils.py +1020 -0
- ins_pricing v2/modelling/watchdog_run.py +211 -0
- ins_pricing v2/pricing/__init__.py +27 -0
- ins_pricing v2/pricing/calibration.py +39 -0
- ins_pricing v2/pricing/data_quality.py +117 -0
- ins_pricing v2/pricing/exposure.py +85 -0
- ins_pricing v2/pricing/factors.py +91 -0
- ins_pricing v2/pricing/monitoring.py +99 -0
- ins_pricing v2/pricing/rate_table.py +78 -0
- ins_pricing v2/production/__init__.py +21 -0
- ins_pricing v2/production/drift.py +30 -0
- ins_pricing v2/production/monitoring.py +143 -0
- ins_pricing v2/production/scoring.py +40 -0
- ins_pricing v2/reporting/__init__.py +11 -0
- ins_pricing v2/reporting/report_builder.py +72 -0
- ins_pricing v2/reporting/scheduler.py +45 -0
- ins_pricing v2/scripts/BayesOpt_incremental.py +722 -0
- ins_pricing v2/scripts/Explain_entry.py +545 -0
- ins_pricing v2/scripts/__init__.py +1 -0
- ins_pricing v2/scripts/train.py +568 -0
- ins_pricing v2/setup.py +55 -0
- ins_pricing v2/smoke_test.py +28 -0
- ins_pricing-0.1.6.dist-info/METADATA +78 -0
- ins_pricing-0.1.6.dist-info/RECORD +169 -0
- ins_pricing-0.1.6.dist-info/WHEEL +5 -0
- ins_pricing-0.1.6.dist-info/top_level.txt +4 -0
- user_packages/__init__.py +105 -0
- user_packages legacy/BayesOpt.py +5659 -0
- user_packages legacy/BayesOpt_entry.py +513 -0
- user_packages legacy/BayesOpt_incremental.py +685 -0
- user_packages legacy/Pricing_Run.py +36 -0
- user_packages legacy/Try/BayesOpt Legacy251213.py +3719 -0
- user_packages legacy/Try/BayesOpt Legacy251215.py +3758 -0
- user_packages legacy/Try/BayesOpt lagecy251201.py +3506 -0
- user_packages legacy/Try/BayesOpt lagecy251218.py +3992 -0
- user_packages legacy/Try/BayesOpt legacy.py +3280 -0
- user_packages legacy/Try/BayesOpt.py +838 -0
- user_packages legacy/Try/BayesOptAll.py +1569 -0
- user_packages legacy/Try/BayesOptAllPlatform.py +909 -0
- user_packages legacy/Try/BayesOptCPUGPU.py +1877 -0
- user_packages legacy/Try/BayesOptSearch.py +830 -0
- user_packages legacy/Try/BayesOptSearchOrigin.py +829 -0
- user_packages legacy/Try/BayesOptV1.py +1911 -0
- user_packages legacy/Try/BayesOptV10.py +2973 -0
- user_packages legacy/Try/BayesOptV11.py +3001 -0
- user_packages legacy/Try/BayesOptV12.py +3001 -0
- user_packages legacy/Try/BayesOptV2.py +2065 -0
- user_packages legacy/Try/BayesOptV3.py +2209 -0
- user_packages legacy/Try/BayesOptV4.py +2342 -0
- user_packages legacy/Try/BayesOptV5.py +2372 -0
- user_packages legacy/Try/BayesOptV6.py +2759 -0
- user_packages legacy/Try/BayesOptV7.py +2832 -0
- user_packages legacy/Try/BayesOptV8Codex.py +2731 -0
- user_packages legacy/Try/BayesOptV8Gemini.py +2614 -0
- user_packages legacy/Try/BayesOptV9.py +2927 -0
- user_packages legacy/Try/BayesOpt_entry legacy.py +313 -0
- user_packages legacy/Try/ModelBayesOptSearch.py +359 -0
- user_packages legacy/Try/ResNetBayesOptSearch.py +249 -0
- user_packages legacy/Try/XgbBayesOptSearch.py +121 -0
- user_packages legacy/Try/xgbbayesopt.py +523 -0
- user_packages legacy/__init__.py +19 -0
- user_packages legacy/cli_common.py +124 -0
- user_packages legacy/notebook_utils.py +228 -0
- user_packages legacy/watchdog_run.py +202 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI entry point generated from BayesOpt_AutoPricing.ipynb so the workflow can
|
|
3
|
+
run non‑interactively (e.g., via torchrun).
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
python -m torch.distributed.run --standalone --nproc_per_node=2 \\
|
|
7
|
+
ins_pricing/modelling/BayesOpt_entry.py \\
|
|
8
|
+
--config-json ins_pricing/modelling/demo/config_template.json \\
|
|
9
|
+
--model-keys ft --max-evals 50 --use-ft-ddp
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List
|
|
18
|
+
|
|
19
|
+
import pandas as pd
|
|
20
|
+
from sklearn.model_selection import train_test_split
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from . import bayesopt as ropt # type: ignore
|
|
24
|
+
from .cli_common import ( # type: ignore
|
|
25
|
+
PLOT_MODEL_LABELS,
|
|
26
|
+
PYTORCH_TRAINERS,
|
|
27
|
+
build_model_names,
|
|
28
|
+
dedupe_preserve_order,
|
|
29
|
+
load_config_json,
|
|
30
|
+
normalize_config_paths,
|
|
31
|
+
parse_model_pairs,
|
|
32
|
+
resolve_config_path,
|
|
33
|
+
resolve_path,
|
|
34
|
+
set_env,
|
|
35
|
+
)
|
|
36
|
+
except Exception: # pragma: no cover
|
|
37
|
+
try:
|
|
38
|
+
import bayesopt as ropt # type: ignore
|
|
39
|
+
from cli_common import ( # type: ignore
|
|
40
|
+
PLOT_MODEL_LABELS,
|
|
41
|
+
PYTORCH_TRAINERS,
|
|
42
|
+
build_model_names,
|
|
43
|
+
dedupe_preserve_order,
|
|
44
|
+
load_config_json,
|
|
45
|
+
normalize_config_paths,
|
|
46
|
+
parse_model_pairs,
|
|
47
|
+
resolve_config_path,
|
|
48
|
+
resolve_path,
|
|
49
|
+
set_env,
|
|
50
|
+
)
|
|
51
|
+
except Exception:
|
|
52
|
+
try:
|
|
53
|
+
import ins_pricing.bayesopt as ropt # type: ignore
|
|
54
|
+
from ins_pricing.cli_common import ( # type: ignore
|
|
55
|
+
PLOT_MODEL_LABELS,
|
|
56
|
+
PYTORCH_TRAINERS,
|
|
57
|
+
build_model_names,
|
|
58
|
+
dedupe_preserve_order,
|
|
59
|
+
load_config_json,
|
|
60
|
+
normalize_config_paths,
|
|
61
|
+
parse_model_pairs,
|
|
62
|
+
resolve_config_path,
|
|
63
|
+
resolve_path,
|
|
64
|
+
set_env,
|
|
65
|
+
)
|
|
66
|
+
except Exception:
|
|
67
|
+
import BayesOpt as ropt # type: ignore
|
|
68
|
+
from cli_common import ( # type: ignore
|
|
69
|
+
PLOT_MODEL_LABELS,
|
|
70
|
+
PYTORCH_TRAINERS,
|
|
71
|
+
build_model_names,
|
|
72
|
+
dedupe_preserve_order,
|
|
73
|
+
load_config_json,
|
|
74
|
+
normalize_config_paths,
|
|
75
|
+
parse_model_pairs,
|
|
76
|
+
resolve_config_path,
|
|
77
|
+
resolve_path,
|
|
78
|
+
set_env,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
import matplotlib
|
|
82
|
+
|
|
83
|
+
if os.name != "nt" and not os.environ.get("DISPLAY") and not os.environ.get("MPLBACKEND"):
|
|
84
|
+
matplotlib.use("Agg")
|
|
85
|
+
import matplotlib.pyplot as plt
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
from .run_logging import configure_run_logging # type: ignore
|
|
89
|
+
except Exception: # pragma: no cover
|
|
90
|
+
try:
|
|
91
|
+
from run_logging import configure_run_logging # type: ignore
|
|
92
|
+
except Exception: # pragma: no cover
|
|
93
|
+
configure_run_logging = None # type: ignore
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
from .plotting.diagnostics import plot_loss_curve as plot_loss_curve_common
|
|
97
|
+
except Exception: # pragma: no cover
|
|
98
|
+
try:
|
|
99
|
+
from ins_pricing.plotting.diagnostics import plot_loss_curve as plot_loss_curve_common
|
|
100
|
+
except Exception: # pragma: no cover
|
|
101
|
+
plot_loss_curve_common = None
|
|
102
|
+
|
|
103
|
+
def _parse_args() -> argparse.Namespace:
|
|
104
|
+
parser = argparse.ArgumentParser(
|
|
105
|
+
description="Batch trainer generated from BayesOpt_AutoPricing notebook."
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--config-json",
|
|
109
|
+
required=True,
|
|
110
|
+
help="Path to the JSON config describing datasets and feature columns.",
|
|
111
|
+
)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--model-keys",
|
|
114
|
+
nargs="+",
|
|
115
|
+
default=["ft"],
|
|
116
|
+
choices=["glm", "xgb", "resn", "ft", "gnn", "all"],
|
|
117
|
+
help="Space-separated list of trainers to run (e.g., --model-keys glm xgb). Include 'all' to run every trainer.",
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--stack-model-keys",
|
|
121
|
+
nargs="+",
|
|
122
|
+
default=None,
|
|
123
|
+
choices=["glm", "xgb", "resn", "ft", "gnn", "all"],
|
|
124
|
+
help=(
|
|
125
|
+
"Only used when ft_role != 'model' (FT runs as feature generator). "
|
|
126
|
+
"When provided (or when config defines stack_model_keys), these trainers run after FT features "
|
|
127
|
+
"are generated. Use 'all' to run every non-FT trainer."
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--max-evals",
|
|
132
|
+
type=int,
|
|
133
|
+
default=50,
|
|
134
|
+
help="Optuna trial count per dataset.",
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--use-resn-ddp",
|
|
138
|
+
action="store_true",
|
|
139
|
+
help="Force ResNet trainer to use DistributedDataParallel.",
|
|
140
|
+
)
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
"--use-ft-ddp",
|
|
143
|
+
action="store_true",
|
|
144
|
+
help="Force FT-Transformer trainer to use DistributedDataParallel.",
|
|
145
|
+
)
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--use-resn-dp",
|
|
148
|
+
action="store_true",
|
|
149
|
+
help="Enable ResNet DataParallel fall-back regardless of config.",
|
|
150
|
+
)
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--use-ft-dp",
|
|
153
|
+
action="store_true",
|
|
154
|
+
help="Enable FT-Transformer DataParallel fall-back regardless of config.",
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"--use-gnn-dp",
|
|
158
|
+
action="store_true",
|
|
159
|
+
help="Enable GNN DataParallel fall-back regardless of config.",
|
|
160
|
+
)
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"--use-gnn-ddp",
|
|
163
|
+
action="store_true",
|
|
164
|
+
help="Force GNN trainer to use DistributedDataParallel.",
|
|
165
|
+
)
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--gnn-no-ann",
|
|
168
|
+
action="store_true",
|
|
169
|
+
help="Disable approximate k-NN for GNN graph construction and use exact search.",
|
|
170
|
+
)
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"--gnn-ann-threshold",
|
|
173
|
+
type=int,
|
|
174
|
+
default=None,
|
|
175
|
+
help="Row threshold above which approximate k-NN is preferred (overrides config).",
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"--gnn-graph-cache",
|
|
179
|
+
default=None,
|
|
180
|
+
help="Optional path to persist/load cached adjacency matrix for GNN.",
|
|
181
|
+
)
|
|
182
|
+
parser.add_argument(
|
|
183
|
+
"--gnn-max-gpu-nodes",
|
|
184
|
+
type=int,
|
|
185
|
+
default=None,
|
|
186
|
+
help="Overrides the maximum node count allowed for GPU k-NN graph construction.",
|
|
187
|
+
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--gnn-gpu-mem-ratio",
|
|
190
|
+
type=float,
|
|
191
|
+
default=None,
|
|
192
|
+
help="Overrides the fraction of free GPU memory the k-NN builder may consume.",
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
"--gnn-gpu-mem-overhead",
|
|
196
|
+
type=float,
|
|
197
|
+
default=None,
|
|
198
|
+
help="Overrides the temporary GPU memory overhead multiplier for k-NN estimation.",
|
|
199
|
+
)
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
"--output-dir",
|
|
202
|
+
default=None,
|
|
203
|
+
help="Override output root for models/results/plots.",
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
"--plot-curves",
|
|
207
|
+
action="store_true",
|
|
208
|
+
help="Enable lift/diagnostic plots after training (config file may also request plotting).",
|
|
209
|
+
)
|
|
210
|
+
parser.add_argument(
|
|
211
|
+
"--ft-as-feature",
|
|
212
|
+
action="store_true",
|
|
213
|
+
help="Alias for --ft-role embedding (keep tuning, export embeddings; skip FT plots/SHAP).",
|
|
214
|
+
)
|
|
215
|
+
parser.add_argument(
|
|
216
|
+
"--ft-role",
|
|
217
|
+
default=None,
|
|
218
|
+
choices=["model", "embedding", "unsupervised_embedding"],
|
|
219
|
+
help="How to use FT: model (default), embedding (export pooling embeddings), or unsupervised_embedding.",
|
|
220
|
+
)
|
|
221
|
+
parser.add_argument(
|
|
222
|
+
"--ft-feature-prefix",
|
|
223
|
+
default="ft_feat",
|
|
224
|
+
help="Prefix used for generated FT features (columns: pred_<prefix>_0.. or pred_<prefix>).",
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--reuse-best-params",
|
|
228
|
+
action="store_true",
|
|
229
|
+
help="Skip Optuna and reuse best_params saved in Results/versions or bestparams CSV when available.",
|
|
230
|
+
)
|
|
231
|
+
return parser.parse_args()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _plot_curves_for_model(model: ropt.BayesOptModel, trained_keys: List[str], cfg: Dict) -> None:
|
|
235
|
+
plot_cfg = cfg.get("plot", {})
|
|
236
|
+
legacy_lift_flags = {
|
|
237
|
+
"glm": cfg.get("plot_lift_glm", False),
|
|
238
|
+
"xgb": cfg.get("plot_lift_xgb", False),
|
|
239
|
+
"resn": cfg.get("plot_lift_resn", False),
|
|
240
|
+
"ft": cfg.get("plot_lift_ft", False),
|
|
241
|
+
}
|
|
242
|
+
plot_enabled = plot_cfg.get("enable", any(legacy_lift_flags.values()))
|
|
243
|
+
if not plot_enabled:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
n_bins = int(plot_cfg.get("n_bins", 10))
|
|
247
|
+
oneway_enabled = plot_cfg.get("oneway", True)
|
|
248
|
+
|
|
249
|
+
available_models = dedupe_preserve_order(
|
|
250
|
+
[m for m in trained_keys if m in PLOT_MODEL_LABELS]
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if oneway_enabled:
|
|
254
|
+
model.plot_oneway(n_bins=n_bins)
|
|
255
|
+
|
|
256
|
+
if not available_models:
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
lift_models = plot_cfg.get("lift_models")
|
|
260
|
+
if lift_models is None:
|
|
261
|
+
lift_models = [
|
|
262
|
+
m for m, enabled in legacy_lift_flags.items() if enabled]
|
|
263
|
+
if not lift_models:
|
|
264
|
+
lift_models = available_models
|
|
265
|
+
lift_models = dedupe_preserve_order(
|
|
266
|
+
[m for m in lift_models if m in available_models]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
for model_key in lift_models:
|
|
270
|
+
label, pred_nme = PLOT_MODEL_LABELS[model_key]
|
|
271
|
+
model.plot_lift(model_label=label, pred_nme=pred_nme, n_bins=n_bins)
|
|
272
|
+
|
|
273
|
+
if not plot_cfg.get("double_lift", True) or len(available_models) < 2:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
raw_pairs = plot_cfg.get("double_lift_pairs")
|
|
277
|
+
if raw_pairs:
|
|
278
|
+
pairs = [
|
|
279
|
+
(a, b)
|
|
280
|
+
for a, b in parse_model_pairs(raw_pairs)
|
|
281
|
+
if a in available_models and b in available_models and a != b
|
|
282
|
+
]
|
|
283
|
+
else:
|
|
284
|
+
pairs = [(a, b) for i, a in enumerate(available_models) for b in available_models[i + 1 :]]
|
|
285
|
+
|
|
286
|
+
for first, second in pairs:
|
|
287
|
+
model.plot_dlift([first, second], n_bins=n_bins)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _plot_loss_curve_for_trainer(model_name: str, trainer) -> None:
|
|
291
|
+
model_obj = getattr(trainer, "model", None)
|
|
292
|
+
history = None
|
|
293
|
+
if model_obj is not None:
|
|
294
|
+
history = getattr(model_obj, "training_history", None)
|
|
295
|
+
if not history:
|
|
296
|
+
history = getattr(trainer, "training_history", None)
|
|
297
|
+
if not history:
|
|
298
|
+
return
|
|
299
|
+
train_hist = list(history.get("train") or [])
|
|
300
|
+
val_hist = list(history.get("val") or [])
|
|
301
|
+
if not train_hist and not val_hist:
|
|
302
|
+
return
|
|
303
|
+
try:
|
|
304
|
+
plot_dir = trainer.output.plot_path(
|
|
305
|
+
f"loss_{model_name}_{trainer.model_name_prefix}.png"
|
|
306
|
+
)
|
|
307
|
+
except Exception:
|
|
308
|
+
default_dir = Path("plot")
|
|
309
|
+
default_dir.mkdir(parents=True, exist_ok=True)
|
|
310
|
+
plot_dir = str(
|
|
311
|
+
default_dir / f"loss_{model_name}_{trainer.model_name_prefix}.png")
|
|
312
|
+
if plot_loss_curve_common is not None:
|
|
313
|
+
plot_loss_curve_common(
|
|
314
|
+
history=history,
|
|
315
|
+
title=f"{trainer.model_name_prefix} Loss Curve ({model_name})",
|
|
316
|
+
save_path=plot_dir,
|
|
317
|
+
show=False,
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
epochs = range(1, max(len(train_hist), len(val_hist)) + 1)
|
|
321
|
+
fig, ax = plt.subplots(figsize=(8, 4))
|
|
322
|
+
if train_hist:
|
|
323
|
+
ax.plot(range(1, len(train_hist) + 1),
|
|
324
|
+
train_hist, label="Train Loss", color="tab:blue")
|
|
325
|
+
if val_hist:
|
|
326
|
+
ax.plot(range(1, len(val_hist) + 1),
|
|
327
|
+
val_hist, label="Validation Loss", color="tab:orange")
|
|
328
|
+
ax.set_xlabel("Epoch")
|
|
329
|
+
ax.set_ylabel("Weighted Loss")
|
|
330
|
+
ax.set_title(
|
|
331
|
+
f"{trainer.model_name_prefix} Loss Curve ({model_name})")
|
|
332
|
+
ax.grid(True, linestyle="--", alpha=0.3)
|
|
333
|
+
ax.legend()
|
|
334
|
+
plt.tight_layout()
|
|
335
|
+
plt.savefig(plot_dir, dpi=300)
|
|
336
|
+
plt.close(fig)
|
|
337
|
+
print(
|
|
338
|
+
f"[Plot] Saved loss curve for {model_name}/{trainer.label} -> {plot_dir}")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def train_from_config(args: argparse.Namespace) -> None:
|
|
342
|
+
script_dir = Path(__file__).resolve().parent
|
|
343
|
+
config_path = resolve_config_path(args.config_json, script_dir)
|
|
344
|
+
cfg = load_config_json(
|
|
345
|
+
config_path,
|
|
346
|
+
required_keys=["data_dir", "model_list", "model_categories", "target", "weight"],
|
|
347
|
+
)
|
|
348
|
+
cfg = normalize_config_paths(cfg, config_path)
|
|
349
|
+
|
|
350
|
+
set_env(cfg.get("env", {}))
|
|
351
|
+
plot_requested = bool(args.plot_curves or cfg.get("plot_curves", False))
|
|
352
|
+
|
|
353
|
+
def _safe_int_env(key: str, default: int) -> int:
|
|
354
|
+
try:
|
|
355
|
+
return int(os.environ.get(key, default))
|
|
356
|
+
except (TypeError, ValueError):
|
|
357
|
+
return default
|
|
358
|
+
|
|
359
|
+
dist_world_size = _safe_int_env("WORLD_SIZE", 1)
|
|
360
|
+
dist_rank = _safe_int_env("RANK", 0)
|
|
361
|
+
dist_active = dist_world_size > 1
|
|
362
|
+
|
|
363
|
+
data_dir = Path(cfg["data_dir"])
|
|
364
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
365
|
+
|
|
366
|
+
prop_test = cfg.get("prop_test", 0.25)
|
|
367
|
+
rand_seed = cfg.get("rand_seed", 13)
|
|
368
|
+
epochs = cfg.get("epochs", 50)
|
|
369
|
+
output_dir = args.output_dir or cfg.get("output_dir")
|
|
370
|
+
if isinstance(output_dir, str) and output_dir.strip():
|
|
371
|
+
resolved = resolve_path(output_dir, config_path.parent)
|
|
372
|
+
if resolved is not None:
|
|
373
|
+
output_dir = str(resolved)
|
|
374
|
+
reuse_best_params = bool(args.reuse_best_params or cfg.get("reuse_best_params", False))
|
|
375
|
+
xgb_max_depth_max = int(cfg.get("xgb_max_depth_max", 25))
|
|
376
|
+
xgb_n_estimators_max = int(cfg.get("xgb_n_estimators_max", 500))
|
|
377
|
+
optuna_storage = cfg.get("optuna_storage")
|
|
378
|
+
optuna_study_prefix = cfg.get("optuna_study_prefix")
|
|
379
|
+
best_params_files = cfg.get("best_params_files")
|
|
380
|
+
|
|
381
|
+
model_names = build_model_names(
|
|
382
|
+
cfg["model_list"], cfg["model_categories"])
|
|
383
|
+
if not model_names:
|
|
384
|
+
raise ValueError(
|
|
385
|
+
"No model names generated from model_list/model_categories.")
|
|
386
|
+
|
|
387
|
+
results: Dict[str, ropt.BayesOptModel] = {}
|
|
388
|
+
trained_keys_by_model: Dict[str, List[str]] = {}
|
|
389
|
+
|
|
390
|
+
for model_name in model_names:
|
|
391
|
+
# Per-dataset training loop: load data, split train/test, and train requested models.
|
|
392
|
+
csv_path = data_dir / f"{model_name}.csv"
|
|
393
|
+
if not csv_path.exists():
|
|
394
|
+
raise FileNotFoundError(f"Missing dataset: {csv_path}")
|
|
395
|
+
|
|
396
|
+
print(f"\n=== Processing model {model_name} ===")
|
|
397
|
+
raw = pd.read_csv(csv_path, low_memory=False)
|
|
398
|
+
raw = raw.copy()
|
|
399
|
+
for col in raw.columns:
|
|
400
|
+
s = raw[col]
|
|
401
|
+
if pd.api.types.is_numeric_dtype(s):
|
|
402
|
+
raw[col] = pd.to_numeric(s, errors="coerce").fillna(0)
|
|
403
|
+
else:
|
|
404
|
+
raw[col] = s.astype("object").fillna("<NA>")
|
|
405
|
+
|
|
406
|
+
train_df, test_df = train_test_split(
|
|
407
|
+
raw, test_size=prop_test, random_state=rand_seed
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
use_resn_dp = args.use_resn_dp or cfg.get(
|
|
411
|
+
"use_resn_data_parallel", False)
|
|
412
|
+
use_ft_dp = args.use_ft_dp or cfg.get("use_ft_data_parallel", True)
|
|
413
|
+
use_resn_ddp = args.use_resn_ddp or cfg.get("use_resn_ddp", False)
|
|
414
|
+
use_ft_ddp = args.use_ft_ddp or cfg.get("use_ft_ddp", False)
|
|
415
|
+
use_gnn_dp = args.use_gnn_dp or cfg.get("use_gnn_data_parallel", False)
|
|
416
|
+
use_gnn_ddp = args.use_gnn_ddp or cfg.get("use_gnn_ddp", False)
|
|
417
|
+
gnn_use_ann = cfg.get("gnn_use_approx_knn", True)
|
|
418
|
+
if args.gnn_no_ann:
|
|
419
|
+
gnn_use_ann = False
|
|
420
|
+
gnn_threshold = args.gnn_ann_threshold if args.gnn_ann_threshold is not None else cfg.get(
|
|
421
|
+
"gnn_approx_knn_threshold", 50000)
|
|
422
|
+
gnn_graph_cache = args.gnn_graph_cache or cfg.get("gnn_graph_cache")
|
|
423
|
+
if isinstance(gnn_graph_cache, str) and gnn_graph_cache.strip():
|
|
424
|
+
resolved_cache = resolve_path(gnn_graph_cache, config_path.parent)
|
|
425
|
+
if resolved_cache is not None:
|
|
426
|
+
gnn_graph_cache = str(resolved_cache)
|
|
427
|
+
gnn_max_gpu_nodes = args.gnn_max_gpu_nodes if args.gnn_max_gpu_nodes is not None else cfg.get(
|
|
428
|
+
"gnn_max_gpu_knn_nodes", 200000)
|
|
429
|
+
gnn_gpu_mem_ratio = args.gnn_gpu_mem_ratio if args.gnn_gpu_mem_ratio is not None else cfg.get(
|
|
430
|
+
"gnn_knn_gpu_mem_ratio", 0.9)
|
|
431
|
+
gnn_gpu_mem_overhead = args.gnn_gpu_mem_overhead if args.gnn_gpu_mem_overhead is not None else cfg.get(
|
|
432
|
+
"gnn_knn_gpu_mem_overhead", 2.0)
|
|
433
|
+
|
|
434
|
+
binary_target = cfg.get("binary_target") or cfg.get("binary_resp_nme")
|
|
435
|
+
feature_list = cfg.get("feature_list")
|
|
436
|
+
categorical_features = cfg.get("categorical_features")
|
|
437
|
+
|
|
438
|
+
ft_role = args.ft_role or cfg.get("ft_role", "model")
|
|
439
|
+
if args.ft_as_feature and args.ft_role is None:
|
|
440
|
+
# Keep legacy behavior as a convenience alias only when the config
|
|
441
|
+
# didn't already request a non-default FT role.
|
|
442
|
+
if str(cfg.get("ft_role", "model")) == "model":
|
|
443
|
+
ft_role = "embedding"
|
|
444
|
+
ft_feature_prefix = str(cfg.get("ft_feature_prefix", args.ft_feature_prefix))
|
|
445
|
+
ft_num_numeric_tokens = cfg.get("ft_num_numeric_tokens")
|
|
446
|
+
|
|
447
|
+
model = ropt.BayesOptModel(
|
|
448
|
+
train_df,
|
|
449
|
+
test_df,
|
|
450
|
+
model_name,
|
|
451
|
+
cfg["target"],
|
|
452
|
+
cfg["weight"],
|
|
453
|
+
feature_list,
|
|
454
|
+
binary_resp_nme=binary_target,
|
|
455
|
+
cate_list=categorical_features,
|
|
456
|
+
prop_test=prop_test,
|
|
457
|
+
rand_seed=rand_seed,
|
|
458
|
+
epochs=epochs,
|
|
459
|
+
use_resn_data_parallel=use_resn_dp,
|
|
460
|
+
use_ft_data_parallel=use_ft_dp,
|
|
461
|
+
use_resn_ddp=use_resn_ddp,
|
|
462
|
+
use_ft_ddp=use_ft_ddp,
|
|
463
|
+
use_gnn_data_parallel=use_gnn_dp,
|
|
464
|
+
use_gnn_ddp=use_gnn_ddp,
|
|
465
|
+
output_dir=output_dir,
|
|
466
|
+
xgb_max_depth_max=xgb_max_depth_max,
|
|
467
|
+
xgb_n_estimators_max=xgb_n_estimators_max,
|
|
468
|
+
resn_weight_decay=cfg.get("resn_weight_decay"),
|
|
469
|
+
final_ensemble=bool(cfg.get("final_ensemble", False)),
|
|
470
|
+
final_ensemble_k=int(cfg.get("final_ensemble_k", 3)),
|
|
471
|
+
final_refit=bool(cfg.get("final_refit", True)),
|
|
472
|
+
optuna_storage=optuna_storage,
|
|
473
|
+
optuna_study_prefix=optuna_study_prefix,
|
|
474
|
+
best_params_files=best_params_files,
|
|
475
|
+
gnn_use_approx_knn=gnn_use_ann,
|
|
476
|
+
gnn_approx_knn_threshold=gnn_threshold,
|
|
477
|
+
gnn_graph_cache=gnn_graph_cache,
|
|
478
|
+
gnn_max_gpu_knn_nodes=gnn_max_gpu_nodes,
|
|
479
|
+
gnn_knn_gpu_mem_ratio=gnn_gpu_mem_ratio,
|
|
480
|
+
gnn_knn_gpu_mem_overhead=gnn_gpu_mem_overhead,
|
|
481
|
+
ft_role=ft_role,
|
|
482
|
+
ft_feature_prefix=ft_feature_prefix,
|
|
483
|
+
ft_num_numeric_tokens=ft_num_numeric_tokens,
|
|
484
|
+
infer_categorical_max_unique=int(cfg.get("infer_categorical_max_unique", 50)),
|
|
485
|
+
infer_categorical_max_ratio=float(cfg.get("infer_categorical_max_ratio", 0.05)),
|
|
486
|
+
reuse_best_params=reuse_best_params,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
if "all" in args.model_keys:
|
|
490
|
+
requested_keys = ["glm", "xgb", "resn", "ft", "gnn"]
|
|
491
|
+
else:
|
|
492
|
+
requested_keys = args.model_keys
|
|
493
|
+
requested_keys = dedupe_preserve_order(requested_keys)
|
|
494
|
+
|
|
495
|
+
if ft_role != "model":
|
|
496
|
+
requested_keys = [k for k in requested_keys if k != "ft"]
|
|
497
|
+
if not requested_keys:
|
|
498
|
+
stack_keys = args.stack_model_keys or cfg.get("stack_model_keys")
|
|
499
|
+
if stack_keys:
|
|
500
|
+
if "all" in stack_keys:
|
|
501
|
+
requested_keys = ["glm", "xgb", "resn", "gnn"]
|
|
502
|
+
else:
|
|
503
|
+
requested_keys = [k for k in stack_keys if k != "ft"]
|
|
504
|
+
requested_keys = dedupe_preserve_order(requested_keys)
|
|
505
|
+
if dist_active:
|
|
506
|
+
ft_trainer = model.trainers.get("ft")
|
|
507
|
+
if ft_trainer is None:
|
|
508
|
+
raise ValueError("FT trainer is not available.")
|
|
509
|
+
ft_trainer_uses_ddp = bool(
|
|
510
|
+
getattr(ft_trainer, "enable_distributed_optuna", False))
|
|
511
|
+
if not ft_trainer_uses_ddp:
|
|
512
|
+
raise ValueError(
|
|
513
|
+
"FT embedding under torchrun requires enabling FT DDP (use --use-ft-ddp or set use_ft_ddp=true)."
|
|
514
|
+
)
|
|
515
|
+
missing = [key for key in requested_keys if key not in model.trainers]
|
|
516
|
+
if missing:
|
|
517
|
+
raise ValueError(
|
|
518
|
+
f"Trainer(s) {missing} not available for {model_name}")
|
|
519
|
+
|
|
520
|
+
executed_keys: List[str] = []
|
|
521
|
+
if ft_role != "model":
|
|
522
|
+
print(
|
|
523
|
+
f"Optimizing ft as {ft_role} for {model_name} (max_evals={args.max_evals})")
|
|
524
|
+
model.optimize_model("ft", max_evals=args.max_evals)
|
|
525
|
+
model.trainers["ft"].save()
|
|
526
|
+
if getattr(ropt, "torch", None) is not None and ropt.torch.cuda.is_available():
|
|
527
|
+
ropt.free_cuda()
|
|
528
|
+
for key in requested_keys:
|
|
529
|
+
trainer = model.trainers[key]
|
|
530
|
+
trainer_uses_ddp = bool(
|
|
531
|
+
getattr(trainer, "enable_distributed_optuna", False))
|
|
532
|
+
should_run = True
|
|
533
|
+
if dist_active and not trainer_uses_ddp:
|
|
534
|
+
should_run = dist_rank == 0
|
|
535
|
+
if not should_run:
|
|
536
|
+
print(
|
|
537
|
+
f"[Rank {dist_rank}] Skip {model_name}/{key} because trainer is not DDP-enabled."
|
|
538
|
+
)
|
|
539
|
+
continue
|
|
540
|
+
|
|
541
|
+
print(
|
|
542
|
+
f"Optimizing {key} for {model_name} (max_evals={args.max_evals})")
|
|
543
|
+
model.optimize_model(key, max_evals=args.max_evals)
|
|
544
|
+
model.trainers[key].save()
|
|
545
|
+
_plot_loss_curve_for_trainer(model_name, model.trainers[key])
|
|
546
|
+
if key in PYTORCH_TRAINERS:
|
|
547
|
+
ropt.free_cuda()
|
|
548
|
+
executed_keys.append(key)
|
|
549
|
+
|
|
550
|
+
if not executed_keys:
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
results[model_name] = model
|
|
554
|
+
trained_keys_by_model[model_name] = executed_keys
|
|
555
|
+
|
|
556
|
+
if not plot_requested:
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
for name, model in results.items():
|
|
560
|
+
_plot_curves_for_model(
|
|
561
|
+
model,
|
|
562
|
+
trained_keys_by_model.get(name, []),
|
|
563
|
+
cfg,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def main() -> None:
|
|
568
|
+
if configure_run_logging:
|
|
569
|
+
configure_run_logging(prefix="bayesopt_entry")
|
|
570
|
+
args = _parse_args()
|
|
571
|
+
train_from_config(args)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
if __name__ == "__main__":
|
|
575
|
+
main()
|