ins-pricing 0.5.0__py3-none-any.whl → 0.5.3__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/cli/BayesOpt_entry.py +15 -5
- ins_pricing/cli/BayesOpt_incremental.py +43 -10
- ins_pricing/cli/Explain_Run.py +16 -5
- ins_pricing/cli/Explain_entry.py +29 -8
- ins_pricing/cli/Pricing_Run.py +16 -5
- ins_pricing/cli/bayesopt_entry_runner.py +45 -12
- ins_pricing/cli/utils/bootstrap.py +23 -0
- ins_pricing/cli/utils/cli_config.py +34 -15
- ins_pricing/cli/utils/import_resolver.py +14 -14
- ins_pricing/cli/utils/notebook_utils.py +120 -106
- ins_pricing/cli/watchdog_run.py +15 -5
- ins_pricing/frontend/app.py +132 -61
- ins_pricing/frontend/config_builder.py +33 -0
- ins_pricing/frontend/example_config.json +11 -0
- ins_pricing/frontend/runner.py +340 -388
- ins_pricing/modelling/README.md +1 -1
- ins_pricing/modelling/__init__.py +10 -10
- ins_pricing/modelling/bayesopt/README.md +29 -11
- ins_pricing/modelling/bayesopt/config_components.py +12 -0
- ins_pricing/modelling/bayesopt/config_preprocess.py +50 -13
- ins_pricing/modelling/bayesopt/core.py +47 -19
- ins_pricing/modelling/bayesopt/model_plotting_mixin.py +20 -14
- ins_pricing/modelling/bayesopt/models/model_ft_components.py +349 -342
- ins_pricing/modelling/bayesopt/models/model_ft_trainer.py +11 -5
- ins_pricing/modelling/bayesopt/models/model_gnn.py +20 -14
- ins_pricing/modelling/bayesopt/models/model_resn.py +9 -3
- ins_pricing/modelling/bayesopt/trainers/trainer_base.py +62 -50
- ins_pricing/modelling/bayesopt/trainers/trainer_ft.py +61 -53
- ins_pricing/modelling/bayesopt/trainers/trainer_glm.py +9 -3
- ins_pricing/modelling/bayesopt/trainers/trainer_gnn.py +40 -32
- ins_pricing/modelling/bayesopt/trainers/trainer_resn.py +36 -24
- ins_pricing/modelling/bayesopt/trainers/trainer_xgb.py +240 -37
- ins_pricing/modelling/bayesopt/utils/distributed_utils.py +193 -186
- ins_pricing/modelling/bayesopt/utils/torch_trainer_mixin.py +23 -10
- ins_pricing/pricing/factors.py +67 -56
- ins_pricing/setup.py +1 -1
- ins_pricing/utils/__init__.py +7 -6
- ins_pricing/utils/device.py +45 -24
- ins_pricing/utils/logging.py +34 -1
- ins_pricing/utils/profiling.py +8 -4
- {ins_pricing-0.5.0.dist-info → ins_pricing-0.5.3.dist-info}/METADATA +182 -182
- {ins_pricing-0.5.0.dist-info → ins_pricing-0.5.3.dist-info}/RECORD +44 -43
- {ins_pricing-0.5.0.dist-info → ins_pricing-0.5.3.dist-info}/WHEEL +0 -0
- {ins_pricing-0.5.0.dist-info → ins_pricing-0.5.3.dist-info}/top_level.txt +0 -0
ins_pricing/modelling/README.md
CHANGED
|
@@ -4,7 +4,7 @@ This directory contains reusable training tooling and frameworks centered on Bay
|
|
|
4
4
|
|
|
5
5
|
## Key locations
|
|
6
6
|
|
|
7
|
-
- `
|
|
7
|
+
- `bayesopt/` - core training/tuning package
|
|
8
8
|
- `explain/` - explainability helpers
|
|
9
9
|
- `plotting/` - plotting utilities
|
|
10
10
|
- `ins_pricing/cli/` - CLI entry points
|
|
@@ -19,13 +19,14 @@ __all__ = sorted(
|
|
|
19
19
|
}
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
_LAZY_ATTRS = {
|
|
23
|
-
"bayesopt": "ins_pricing.modelling.bayesopt",
|
|
24
|
-
"plotting": "ins_pricing.modelling.plotting",
|
|
25
|
-
"explain": "ins_pricing.modelling.explain",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
22
|
+
_LAZY_ATTRS = {
|
|
23
|
+
"bayesopt": "ins_pricing.modelling.bayesopt",
|
|
24
|
+
"plotting": "ins_pricing.modelling.plotting",
|
|
25
|
+
"explain": "ins_pricing.modelling.explain",
|
|
26
|
+
"evaluation": "ins_pricing.modelling.evaluation",
|
|
27
|
+
"BayesOptConfig": "ins_pricing.modelling.bayesopt.core",
|
|
28
|
+
"BayesOptModel": "ins_pricing.modelling.bayesopt.core",
|
|
29
|
+
}
|
|
29
30
|
|
|
30
31
|
_BAYESOPT_EXPORTS = {
|
|
31
32
|
"BayesOptConfig",
|
|
@@ -74,7 +75,6 @@ __all__ = sorted(set(__all__) | set(_BAYESOPT_EXPORTS) | set(_LEGACY_EXPORTS))
|
|
|
74
75
|
|
|
75
76
|
_LAZY_SUBMODULES = {
|
|
76
77
|
"bayesopt": "ins_pricing.modelling.bayesopt",
|
|
77
|
-
"evaluation": "ins_pricing.modelling.evaluation",
|
|
78
78
|
"cli": "ins_pricing.cli",
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -124,8 +124,8 @@ def __getattr__(name: str):
|
|
|
124
124
|
target = _LAZY_ATTRS.get(name)
|
|
125
125
|
if target:
|
|
126
126
|
module = import_module(target)
|
|
127
|
-
if name in {"bayesopt", "plotting", "explain"}:
|
|
128
|
-
value = module
|
|
127
|
+
if name in {"bayesopt", "plotting", "explain", "evaluation"}:
|
|
128
|
+
value = module
|
|
129
129
|
else:
|
|
130
130
|
value = getattr(module, name)
|
|
131
131
|
globals()[name] = value
|
|
@@ -46,14 +46,32 @@ python ins_pricing/cli/BayesOpt_entry.py --config-json config_template.json
|
|
|
46
46
|
- `embedding`: FT trains with labels but exports embeddings (`pred_<prefix>_*`).
|
|
47
47
|
- `unsupervised_embedding`: FT trains without labels and exports embeddings.
|
|
48
48
|
|
|
49
|
-
## Output layout
|
|
50
|
-
|
|
51
|
-
`output_dir/` contains:
|
|
52
|
-
- `plot/` plots and diagnostics
|
|
53
|
-
- `Results/` metrics, params, and snapshots
|
|
54
|
-
- `model/` saved models
|
|
55
|
-
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
49
|
+
## Output layout
|
|
50
|
+
|
|
51
|
+
`output_dir/` contains:
|
|
52
|
+
- `plot/` plots and diagnostics
|
|
53
|
+
- `Results/` metrics, params, and snapshots
|
|
54
|
+
- `model/` saved models
|
|
55
|
+
|
|
56
|
+
## XGBoost GPU tips
|
|
57
|
+
|
|
58
|
+
- Use `xgb_gpu_id` to select a specific GPU on multi-GPU Linux systems.
|
|
59
|
+
- Per-fold GPU cleanup is disabled by default to avoid long idle gaps caused by CUDA sync.
|
|
60
|
+
- If you need to reclaim memory between folds, set `xgb_cleanup_per_fold=true`.
|
|
61
|
+
- If you still need a full device sync, set `xgb_cleanup_synchronize=true` (slower).
|
|
62
|
+
- `xgb_use_dmatrix=true` switches XGBoost to `xgb.train` + DMatrix/QuantileDMatrix for better throughput.
|
|
63
|
+
- External-memory DMatrix (file-backed) is disabled; pass in-memory arrays/dataframes.
|
|
64
|
+
|
|
65
|
+
## Torch model cleanup
|
|
66
|
+
|
|
67
|
+
To reduce CPU↔GPU thrash, fold-level cleanup for FT/ResNet/GNN is off by default.
|
|
68
|
+
Enable if you see memory pressure:
|
|
69
|
+
- `ft_cleanup_per_fold`, `ft_cleanup_synchronize`
|
|
70
|
+
- `resn_cleanup_per_fold`, `resn_cleanup_synchronize`
|
|
71
|
+
- `gnn_cleanup_per_fold`, `gnn_cleanup_synchronize`
|
|
72
|
+
- `optuna_cleanup_synchronize` controls whether trial-level cleanup syncs CUDA (default false)
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- Relative paths in config are resolved from the config file directory.
|
|
77
|
+
- For multi-GPU, use `torchrun` and set `runner.nproc_per_node` in config.
|
|
@@ -178,10 +178,18 @@ class XGBoostConfig:
|
|
|
178
178
|
Attributes:
|
|
179
179
|
max_depth_max: Maximum tree depth for hyperparameter tuning
|
|
180
180
|
n_estimators_max: Maximum number of estimators for tuning
|
|
181
|
+
gpu_id: GPU device id for XGBoost (None = default)
|
|
182
|
+
cleanup_per_fold: Whether to cleanup GPU memory after each fold
|
|
183
|
+
cleanup_synchronize: Whether to synchronize CUDA during cleanup
|
|
184
|
+
use_dmatrix: Whether to use xgb.train with DMatrix/QuantileDMatrix
|
|
181
185
|
"""
|
|
182
186
|
|
|
183
187
|
max_depth_max: int = 25
|
|
184
188
|
n_estimators_max: int = 500
|
|
189
|
+
gpu_id: Optional[int] = None
|
|
190
|
+
cleanup_per_fold: bool = False
|
|
191
|
+
cleanup_synchronize: bool = False
|
|
192
|
+
use_dmatrix: bool = True
|
|
185
193
|
|
|
186
194
|
@classmethod
|
|
187
195
|
def from_flat_dict(cls, d: Dict[str, Any]) -> "XGBoostConfig":
|
|
@@ -189,6 +197,10 @@ class XGBoostConfig:
|
|
|
189
197
|
return cls(
|
|
190
198
|
max_depth_max=int(d.get("xgb_max_depth_max", 25)),
|
|
191
199
|
n_estimators_max=int(d.get("xgb_n_estimators_max", 500)),
|
|
200
|
+
gpu_id=d.get("xgb_gpu_id"),
|
|
201
|
+
cleanup_per_fold=bool(d.get("xgb_cleanup_per_fold", False)),
|
|
202
|
+
cleanup_synchronize=bool(d.get("xgb_cleanup_synchronize", False)),
|
|
203
|
+
use_dmatrix=bool(d.get("xgb_use_dmatrix", True)),
|
|
192
204
|
)
|
|
193
205
|
|
|
194
206
|
|
|
@@ -14,6 +14,13 @@ from sklearn.preprocessing import StandardScaler
|
|
|
14
14
|
from ins_pricing.utils.io import IOUtils
|
|
15
15
|
from ins_pricing.utils.losses import normalize_loss_name
|
|
16
16
|
from ins_pricing.exceptions import ConfigurationError, DataValidationError
|
|
17
|
+
from ins_pricing.utils import get_logger, log_print
|
|
18
|
+
|
|
19
|
+
_logger = get_logger("ins_pricing.modelling.bayesopt.config_preprocess")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _log(*args, **kwargs) -> None:
|
|
23
|
+
log_print(_logger, *args, **kwargs)
|
|
17
24
|
|
|
18
25
|
# NOTE: Some CSV exports may contain invisible BOM characters or leading/trailing
|
|
19
26
|
# spaces in column names. Pandas requires exact matches, so we normalize a few
|
|
@@ -86,9 +93,20 @@ class BayesOptConfig:
|
|
|
86
93
|
prop_test: Proportion of data for validation (0.0-1.0)
|
|
87
94
|
rand_seed: Random seed for reproducibility
|
|
88
95
|
epochs: Number of training epochs
|
|
89
|
-
use_gpu: Whether to use GPU acceleration
|
|
90
|
-
xgb_max_depth_max: Maximum tree depth for XGBoost tuning
|
|
91
|
-
xgb_n_estimators_max: Maximum estimators for XGBoost tuning
|
|
96
|
+
use_gpu: Whether to use GPU acceleration
|
|
97
|
+
xgb_max_depth_max: Maximum tree depth for XGBoost tuning
|
|
98
|
+
xgb_n_estimators_max: Maximum estimators for XGBoost tuning
|
|
99
|
+
xgb_gpu_id: GPU device id for XGBoost (None = default)
|
|
100
|
+
xgb_cleanup_per_fold: Whether to cleanup GPU memory after each XGBoost fold
|
|
101
|
+
xgb_cleanup_synchronize: Whether to synchronize CUDA during XGBoost cleanup
|
|
102
|
+
xgb_use_dmatrix: Whether to use xgb.train with DMatrix/QuantileDMatrix
|
|
103
|
+
ft_cleanup_per_fold: Whether to cleanup GPU memory after each FT fold
|
|
104
|
+
ft_cleanup_synchronize: Whether to synchronize CUDA during FT cleanup
|
|
105
|
+
resn_cleanup_per_fold: Whether to cleanup GPU memory after each ResNet fold
|
|
106
|
+
resn_cleanup_synchronize: Whether to synchronize CUDA during ResNet cleanup
|
|
107
|
+
gnn_cleanup_per_fold: Whether to cleanup GPU memory after each GNN fold
|
|
108
|
+
gnn_cleanup_synchronize: Whether to synchronize CUDA during GNN cleanup
|
|
109
|
+
optuna_cleanup_synchronize: Whether to synchronize CUDA during Optuna cleanup
|
|
92
110
|
use_resn_data_parallel: Use DataParallel for ResNet
|
|
93
111
|
use_ft_data_parallel: Use DataParallel for FT-Transformer
|
|
94
112
|
use_resn_ddp: Use DDP for ResNet
|
|
@@ -128,9 +146,20 @@ class BayesOptConfig:
|
|
|
128
146
|
epochs: int = 100
|
|
129
147
|
use_gpu: bool = True
|
|
130
148
|
|
|
131
|
-
# XGBoost settings
|
|
132
|
-
xgb_max_depth_max: int = 25
|
|
133
|
-
xgb_n_estimators_max: int = 500
|
|
149
|
+
# XGBoost settings
|
|
150
|
+
xgb_max_depth_max: int = 25
|
|
151
|
+
xgb_n_estimators_max: int = 500
|
|
152
|
+
xgb_gpu_id: Optional[int] = None
|
|
153
|
+
xgb_cleanup_per_fold: bool = False
|
|
154
|
+
xgb_cleanup_synchronize: bool = False
|
|
155
|
+
xgb_use_dmatrix: bool = True
|
|
156
|
+
ft_cleanup_per_fold: bool = False
|
|
157
|
+
ft_cleanup_synchronize: bool = False
|
|
158
|
+
resn_cleanup_per_fold: bool = False
|
|
159
|
+
resn_cleanup_synchronize: bool = False
|
|
160
|
+
gnn_cleanup_per_fold: bool = False
|
|
161
|
+
gnn_cleanup_synchronize: bool = False
|
|
162
|
+
optuna_cleanup_synchronize: bool = False
|
|
134
163
|
|
|
135
164
|
# Distributed training settings
|
|
136
165
|
use_resn_data_parallel: bool = False
|
|
@@ -244,10 +273,18 @@ class BayesOptConfig:
|
|
|
244
273
|
errors.append(
|
|
245
274
|
f"xgb_max_depth_max must be >= 1, got {self.xgb_max_depth_max}"
|
|
246
275
|
)
|
|
247
|
-
if self.xgb_n_estimators_max < 1:
|
|
248
|
-
errors.append(
|
|
249
|
-
f"xgb_n_estimators_max must be >= 1, got {self.xgb_n_estimators_max}"
|
|
250
|
-
)
|
|
276
|
+
if self.xgb_n_estimators_max < 1:
|
|
277
|
+
errors.append(
|
|
278
|
+
f"xgb_n_estimators_max must be >= 1, got {self.xgb_n_estimators_max}"
|
|
279
|
+
)
|
|
280
|
+
if self.xgb_gpu_id is not None:
|
|
281
|
+
try:
|
|
282
|
+
gpu_id = int(self.xgb_gpu_id)
|
|
283
|
+
except (TypeError, ValueError):
|
|
284
|
+
errors.append(f"xgb_gpu_id must be an integer, got {self.xgb_gpu_id!r}")
|
|
285
|
+
else:
|
|
286
|
+
if gpu_id < 0:
|
|
287
|
+
errors.append(f"xgb_gpu_id must be >= 0, got {gpu_id}")
|
|
251
288
|
|
|
252
289
|
# Validate distributed training: can't use both DataParallel and DDP
|
|
253
290
|
if self.use_resn_data_parallel and self.use_resn_ddp:
|
|
@@ -363,7 +400,7 @@ class VersionManager:
|
|
|
363
400
|
IOUtils.ensure_parent_dir(str(path))
|
|
364
401
|
with open(path, "w", encoding="utf-8") as f:
|
|
365
402
|
json.dump(payload, f, ensure_ascii=False, indent=2, default=str)
|
|
366
|
-
|
|
403
|
+
_log(f"[Version] Saved snapshot: {path}")
|
|
367
404
|
return str(path)
|
|
368
405
|
|
|
369
406
|
def load_latest(self, tag: str) -> Optional[Dict[str, Any]]:
|
|
@@ -377,7 +414,7 @@ class VersionManager:
|
|
|
377
414
|
try:
|
|
378
415
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
379
416
|
except Exception as exc:
|
|
380
|
-
|
|
417
|
+
_log(f"[Version] Failed to load snapshot {path}: {exc}")
|
|
381
418
|
return None
|
|
382
419
|
|
|
383
420
|
|
|
@@ -469,7 +506,7 @@ class DatasetPreprocessor:
|
|
|
469
506
|
|
|
470
507
|
build_oht = bool(getattr(cfg, "build_oht", True))
|
|
471
508
|
if not build_oht:
|
|
472
|
-
|
|
509
|
+
_log("[Preprocess] build_oht=False; skip one-hot features.", flush=True)
|
|
473
510
|
self.train_oht_data = None
|
|
474
511
|
self.test_oht_data = None
|
|
475
512
|
self.train_oht_scl_data = None
|
|
@@ -16,7 +16,7 @@ from ins_pricing.modelling.bayesopt.model_explain_mixin import BayesOptExplainMi
|
|
|
16
16
|
from ins_pricing.modelling.bayesopt.model_plotting_mixin import BayesOptPlottingMixin
|
|
17
17
|
from ins_pricing.modelling.bayesopt.models import GraphNeuralNetSklearn
|
|
18
18
|
from ins_pricing.modelling.bayesopt.trainers import FTTrainer, GLMTrainer, GNNTrainer, ResNetTrainer, XGBTrainer
|
|
19
|
-
from ins_pricing.utils import EPS, infer_factor_and_cate_list, set_global_seed
|
|
19
|
+
from ins_pricing.utils import EPS, infer_factor_and_cate_list, set_global_seed, get_logger, log_print
|
|
20
20
|
from ins_pricing.utils.io import IOUtils
|
|
21
21
|
from ins_pricing.utils.losses import (
|
|
22
22
|
infer_loss_name_from_model_name,
|
|
@@ -25,6 +25,12 @@ from ins_pricing.utils.losses import (
|
|
|
25
25
|
resolve_xgb_objective,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
_logger = get_logger("ins_pricing.modelling.bayesopt.core")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _log(*args, **kwargs) -> None:
|
|
32
|
+
log_print(_logger, *args, **kwargs)
|
|
33
|
+
|
|
28
34
|
|
|
29
35
|
class _CVSplitter:
|
|
30
36
|
"""Wrapper to carry optional groups or time order for CV splits."""
|
|
@@ -108,11 +114,22 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
108
114
|
region_effect_alpha: Optional[float] = None,
|
|
109
115
|
geo_feature_nmes: Optional[List[str]] = None,
|
|
110
116
|
geo_token_hidden_dim: Optional[int] = None,
|
|
111
|
-
geo_token_layers: Optional[int] = None,
|
|
112
|
-
geo_token_dropout: Optional[float] = None,
|
|
113
|
-
geo_token_k_neighbors: Optional[int] = None,
|
|
114
|
-
geo_token_learning_rate: Optional[float] = None,
|
|
115
|
-
geo_token_epochs: Optional[int] = None
|
|
117
|
+
geo_token_layers: Optional[int] = None,
|
|
118
|
+
geo_token_dropout: Optional[float] = None,
|
|
119
|
+
geo_token_k_neighbors: Optional[int] = None,
|
|
120
|
+
geo_token_learning_rate: Optional[float] = None,
|
|
121
|
+
geo_token_epochs: Optional[int] = None,
|
|
122
|
+
xgb_gpu_id: Optional[int] = None,
|
|
123
|
+
xgb_cleanup_per_fold: bool = False,
|
|
124
|
+
xgb_cleanup_synchronize: bool = False,
|
|
125
|
+
xgb_use_dmatrix: bool = True,
|
|
126
|
+
ft_cleanup_per_fold: bool = False,
|
|
127
|
+
ft_cleanup_synchronize: bool = False,
|
|
128
|
+
resn_cleanup_per_fold: bool = False,
|
|
129
|
+
resn_cleanup_synchronize: bool = False,
|
|
130
|
+
gnn_cleanup_per_fold: bool = False,
|
|
131
|
+
gnn_cleanup_synchronize: bool = False,
|
|
132
|
+
optuna_cleanup_synchronize: bool = False):
|
|
116
133
|
"""Orchestrate BayesOpt training across multiple trainers.
|
|
117
134
|
|
|
118
135
|
Args:
|
|
@@ -229,8 +246,19 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
229
246
|
rand_seed=rand_seed,
|
|
230
247
|
epochs=epochs,
|
|
231
248
|
use_gpu=use_gpu,
|
|
232
|
-
xgb_max_depth_max=int(xgb_max_depth_max),
|
|
233
|
-
xgb_n_estimators_max=int(xgb_n_estimators_max),
|
|
249
|
+
xgb_max_depth_max=int(xgb_max_depth_max),
|
|
250
|
+
xgb_n_estimators_max=int(xgb_n_estimators_max),
|
|
251
|
+
xgb_gpu_id=int(xgb_gpu_id) if xgb_gpu_id is not None else None,
|
|
252
|
+
xgb_cleanup_per_fold=bool(xgb_cleanup_per_fold),
|
|
253
|
+
xgb_cleanup_synchronize=bool(xgb_cleanup_synchronize),
|
|
254
|
+
xgb_use_dmatrix=bool(xgb_use_dmatrix),
|
|
255
|
+
ft_cleanup_per_fold=bool(ft_cleanup_per_fold),
|
|
256
|
+
ft_cleanup_synchronize=bool(ft_cleanup_synchronize),
|
|
257
|
+
resn_cleanup_per_fold=bool(resn_cleanup_per_fold),
|
|
258
|
+
resn_cleanup_synchronize=bool(resn_cleanup_synchronize),
|
|
259
|
+
gnn_cleanup_per_fold=bool(gnn_cleanup_per_fold),
|
|
260
|
+
gnn_cleanup_synchronize=bool(gnn_cleanup_synchronize),
|
|
261
|
+
optuna_cleanup_synchronize=bool(optuna_cleanup_synchronize),
|
|
234
262
|
use_resn_data_parallel=use_resn_data_parallel,
|
|
235
263
|
use_ft_data_parallel=use_ft_data_parallel,
|
|
236
264
|
use_resn_ddp=use_resn_ddp,
|
|
@@ -542,7 +570,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
542
570
|
test_embed, index=self.test_data.index, columns=cols)
|
|
543
571
|
return train_tokens, test_tokens, cols, geo_gnn
|
|
544
572
|
except Exception as exc:
|
|
545
|
-
|
|
573
|
+
_log(f"[GeoToken] Generation failed: {exc}")
|
|
546
574
|
return None
|
|
547
575
|
|
|
548
576
|
def _prepare_geo_tokens(self) -> None:
|
|
@@ -553,7 +581,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
553
581
|
gnn_trainer.prepare_geo_tokens(force=False) # type: ignore[attr-defined]
|
|
554
582
|
return
|
|
555
583
|
except Exception as exc:
|
|
556
|
-
|
|
584
|
+
_log(f"[GeoToken] GNNTrainer generation failed: {exc}")
|
|
557
585
|
|
|
558
586
|
result = self._build_geo_tokens()
|
|
559
587
|
if result is None:
|
|
@@ -563,7 +591,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
563
591
|
self.test_geo_tokens = test_tokens
|
|
564
592
|
self.geo_token_cols = cols
|
|
565
593
|
self.geo_gnn_model = geo_gnn
|
|
566
|
-
|
|
594
|
+
_log(f"[GeoToken] Generated {len(cols)}-dim geo tokens; injecting into FT.")
|
|
567
595
|
|
|
568
596
|
def _add_region_effect(self) -> None:
|
|
569
597
|
"""Partial pooling over province/city to create a smoothed region_effect feature."""
|
|
@@ -573,7 +601,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
573
601
|
return
|
|
574
602
|
for col in [prov_col, city_col]:
|
|
575
603
|
if col not in self.train_data.columns:
|
|
576
|
-
|
|
604
|
+
_log(f"[RegionEffect] Missing column {col}; skipped.")
|
|
577
605
|
return
|
|
578
606
|
|
|
579
607
|
def safe_mean(df: pd.DataFrame) -> float:
|
|
@@ -691,7 +719,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
691
719
|
if best_params_file and not trainer.best_params:
|
|
692
720
|
trainer.best_params = IOUtils.load_params_file(best_params_file)
|
|
693
721
|
trainer.best_trial = None
|
|
694
|
-
|
|
722
|
+
_log(
|
|
695
723
|
f"[Optuna][{trainer.label}] Loaded best_params from {best_params_file}; skip tuning."
|
|
696
724
|
)
|
|
697
725
|
|
|
@@ -705,7 +733,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
705
733
|
trainer.best_trial = None
|
|
706
734
|
trainer.study_name = payload.get(
|
|
707
735
|
"study_name") if isinstance(payload, dict) else None
|
|
708
|
-
|
|
736
|
+
_log(
|
|
709
737
|
f"[Optuna][{trainer.label}] Reusing best_params from versions snapshot.")
|
|
710
738
|
return
|
|
711
739
|
|
|
@@ -716,7 +744,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
716
744
|
try:
|
|
717
745
|
trainer.best_params = IOUtils.load_params_file(params_path)
|
|
718
746
|
trainer.best_trial = None
|
|
719
|
-
|
|
747
|
+
_log(
|
|
720
748
|
f"[Optuna][{trainer.label}] Reusing best_params from {params_path}.")
|
|
721
749
|
except ValueError:
|
|
722
750
|
# Legacy compatibility: ignore empty files and continue tuning.
|
|
@@ -725,7 +753,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
725
753
|
# Generic optimization entry point.
|
|
726
754
|
def optimize_model(self, model_key: str, max_evals: int = 100):
|
|
727
755
|
if model_key not in self.trainers:
|
|
728
|
-
|
|
756
|
+
_log(f"Warning: Unknown model key: {model_key}")
|
|
729
757
|
return
|
|
730
758
|
|
|
731
759
|
trainer = self._require_trainer(model_key)
|
|
@@ -774,7 +802,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
774
802
|
elif hasattr(trainer, "ensemble_predict"):
|
|
775
803
|
trainer.ensemble_predict(k)
|
|
776
804
|
else:
|
|
777
|
-
|
|
805
|
+
_log(
|
|
778
806
|
f"[Ensemble] Trainer '{model_key}' does not support ensemble prediction.",
|
|
779
807
|
flush=True,
|
|
780
808
|
)
|
|
@@ -945,7 +973,7 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
945
973
|
self.trainers[key].save()
|
|
946
974
|
else:
|
|
947
975
|
if model_name: # Only warn when the user specifies a model name.
|
|
948
|
-
|
|
976
|
+
_log(f"[save_model] Warning: Unknown model key {key}")
|
|
949
977
|
|
|
950
978
|
def load_model(self, model_name=None):
|
|
951
979
|
keys = [model_name] if model_name else self.trainers.keys()
|
|
@@ -962,4 +990,4 @@ class BayesOptModel(BayesOptPlottingMixin, BayesOptExplainMixin):
|
|
|
962
990
|
setattr(self, f"{key}_load", trainer.model)
|
|
963
991
|
else:
|
|
964
992
|
if model_name:
|
|
965
|
-
|
|
993
|
+
_log(f"[load_model] Warning: Unknown model key {key}")
|
|
@@ -16,7 +16,13 @@ except Exception as exc: # pragma: no cover - optional dependency
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import pandas as pd
|
|
18
18
|
|
|
19
|
-
from ins_pricing.utils import EPS
|
|
19
|
+
from ins_pricing.utils import EPS, get_logger, log_print
|
|
20
|
+
|
|
21
|
+
_logger = get_logger("ins_pricing.modelling.bayesopt.model_plotting_mixin")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _log(*args, **kwargs) -> None:
|
|
25
|
+
log_print(_logger, *args, **kwargs)
|
|
20
26
|
|
|
21
27
|
try:
|
|
22
28
|
from ins_pricing.modelling.plotting import curves as plot_curves
|
|
@@ -36,9 +42,9 @@ except Exception: # pragma: no cover - optional for legacy imports
|
|
|
36
42
|
|
|
37
43
|
def _plot_skip(label: str) -> None:
|
|
38
44
|
if _MPL_IMPORT_ERROR is not None:
|
|
39
|
-
|
|
45
|
+
_log(f"[Plot] Skip {label}: matplotlib unavailable ({_MPL_IMPORT_ERROR}).", flush=True)
|
|
40
46
|
else:
|
|
41
|
-
|
|
47
|
+
_log(f"[Plot] Skip {label}: matplotlib unavailable.", flush=True)
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
class BayesOptPlottingMixin:
|
|
@@ -54,7 +60,7 @@ class BayesOptPlottingMixin:
|
|
|
54
60
|
_plot_skip("oneway plot")
|
|
55
61
|
return
|
|
56
62
|
if pred_col is not None and pred_col not in self.train_data.columns:
|
|
57
|
-
|
|
63
|
+
_log(
|
|
58
64
|
f"[Oneway] Missing prediction column '{pred_col}'; skip predicted line.",
|
|
59
65
|
flush=True,
|
|
60
66
|
)
|
|
@@ -146,7 +152,7 @@ class BayesOptPlottingMixin:
|
|
|
146
152
|
return
|
|
147
153
|
|
|
148
154
|
if "w_act" not in self.train_data.columns:
|
|
149
|
-
|
|
155
|
+
_log("[Oneway] Missing w_act column; skip plotting.", flush=True)
|
|
150
156
|
return
|
|
151
157
|
|
|
152
158
|
for c in self.factor_nmes:
|
|
@@ -220,7 +226,7 @@ class BayesOptPlottingMixin:
|
|
|
220
226
|
('Lift Chart on Test Data', self.test_data),
|
|
221
227
|
]:
|
|
222
228
|
if 'w_act' not in data.columns or data['w_act'].isna().all():
|
|
223
|
-
|
|
229
|
+
_log(
|
|
224
230
|
f"[Lift] Missing labels for {title}; skip.",
|
|
225
231
|
flush=True,
|
|
226
232
|
)
|
|
@@ -228,7 +234,7 @@ class BayesOptPlottingMixin:
|
|
|
228
234
|
datasets.append((title, data))
|
|
229
235
|
|
|
230
236
|
if not datasets:
|
|
231
|
-
|
|
237
|
+
_log("[Lift] No labeled data available; skip plotting.", flush=True)
|
|
232
238
|
return
|
|
233
239
|
|
|
234
240
|
if plot_curves is None:
|
|
@@ -250,7 +256,7 @@ class BayesOptPlottingMixin:
|
|
|
250
256
|
denom = np.maximum(data[self.weight_nme].values, EPS)
|
|
251
257
|
pred_vals = data[w_pred_col].values / denom
|
|
252
258
|
if pred_vals is None:
|
|
253
|
-
|
|
259
|
+
_log(
|
|
254
260
|
f"[Lift] Missing prediction columns in {title}; skip.",
|
|
255
261
|
flush=True,
|
|
256
262
|
)
|
|
@@ -313,7 +319,7 @@ class BayesOptPlottingMixin:
|
|
|
313
319
|
for data_name, data in [('Train Data', self.train_data),
|
|
314
320
|
('Test Data', self.test_data)]:
|
|
315
321
|
if 'w_act' not in data.columns or data['w_act'].isna().all():
|
|
316
|
-
|
|
322
|
+
_log(
|
|
317
323
|
f"[Double Lift] Missing labels for {data_name}; skip.",
|
|
318
324
|
flush=True,
|
|
319
325
|
)
|
|
@@ -321,7 +327,7 @@ class BayesOptPlottingMixin:
|
|
|
321
327
|
datasets.append((data_name, data))
|
|
322
328
|
|
|
323
329
|
if not datasets:
|
|
324
|
-
|
|
330
|
+
_log("[Double Lift] No labeled data available; skip plotting.", flush=True)
|
|
325
331
|
return
|
|
326
332
|
|
|
327
333
|
if plot_curves is None:
|
|
@@ -358,7 +364,7 @@ class BayesOptPlottingMixin:
|
|
|
358
364
|
pred2 = data[w_pred2_col].values / np.maximum(weight_vals, EPS)
|
|
359
365
|
|
|
360
366
|
if pred1 is None or pred2 is None:
|
|
361
|
-
|
|
367
|
+
_log(
|
|
362
368
|
f"Warning: missing pred_{name1}/pred_{name2} or w_pred columns in {data_name}. Skip plot.")
|
|
363
369
|
continue
|
|
364
370
|
|
|
@@ -395,7 +401,7 @@ class BayesOptPlottingMixin:
|
|
|
395
401
|
_plot_skip("conversion lift plot")
|
|
396
402
|
return
|
|
397
403
|
if not self.binary_resp_nme:
|
|
398
|
-
|
|
404
|
+
_log("Error: `binary_resp_nme` not provided at BayesOptModel init; cannot plot conversion lift.")
|
|
399
405
|
return
|
|
400
406
|
|
|
401
407
|
if plot_curves is None:
|
|
@@ -407,7 +413,7 @@ class BayesOptPlottingMixin:
|
|
|
407
413
|
|
|
408
414
|
for ax, (data_name, data) in zip(axes, datasets.items()):
|
|
409
415
|
if model_pred_col not in data.columns:
|
|
410
|
-
|
|
416
|
+
_log(f"Warning: missing prediction column '{model_pred_col}' in {data_name}. Skip plot.")
|
|
411
417
|
continue
|
|
412
418
|
|
|
413
419
|
# Sort by model prediction and compute bins.
|
|
@@ -463,7 +469,7 @@ class BayesOptPlottingMixin:
|
|
|
463
469
|
|
|
464
470
|
for ax, (data_name, data) in zip(axes, datasets.items()):
|
|
465
471
|
if model_pred_col not in data.columns:
|
|
466
|
-
|
|
472
|
+
_log(f"Warning: missing prediction column '{model_pred_col}' in {data_name}. Skip plot.")
|
|
467
473
|
continue
|
|
468
474
|
|
|
469
475
|
plot_curves.plot_conversion_lift(
|