ins-pricing 0.4.5__py3-none-any.whl → 0.5.1__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.
Files changed (93) hide show
  1. ins_pricing/README.md +48 -22
  2. ins_pricing/__init__.py +142 -90
  3. ins_pricing/cli/BayesOpt_entry.py +58 -46
  4. ins_pricing/cli/BayesOpt_incremental.py +77 -110
  5. ins_pricing/cli/Explain_Run.py +42 -23
  6. ins_pricing/cli/Explain_entry.py +551 -577
  7. ins_pricing/cli/Pricing_Run.py +42 -23
  8. ins_pricing/cli/bayesopt_entry_runner.py +51 -16
  9. ins_pricing/cli/utils/bootstrap.py +23 -0
  10. ins_pricing/cli/utils/cli_common.py +256 -256
  11. ins_pricing/cli/utils/cli_config.py +379 -360
  12. ins_pricing/cli/utils/import_resolver.py +375 -358
  13. ins_pricing/cli/utils/notebook_utils.py +256 -242
  14. ins_pricing/cli/watchdog_run.py +216 -198
  15. ins_pricing/frontend/__init__.py +10 -10
  16. ins_pricing/frontend/app.py +132 -61
  17. ins_pricing/frontend/config_builder.py +33 -0
  18. ins_pricing/frontend/example_config.json +11 -0
  19. ins_pricing/frontend/example_workflows.py +1 -1
  20. ins_pricing/frontend/runner.py +340 -388
  21. ins_pricing/governance/__init__.py +20 -20
  22. ins_pricing/governance/release.py +159 -159
  23. ins_pricing/modelling/README.md +1 -1
  24. ins_pricing/modelling/__init__.py +147 -92
  25. ins_pricing/modelling/{core/bayesopt → bayesopt}/README.md +31 -13
  26. ins_pricing/modelling/{core/bayesopt → bayesopt}/__init__.py +64 -102
  27. ins_pricing/modelling/{core/bayesopt → bayesopt}/config_components.py +12 -0
  28. ins_pricing/modelling/{core/bayesopt → bayesopt}/config_preprocess.py +589 -552
  29. ins_pricing/modelling/{core/bayesopt → bayesopt}/core.py +987 -958
  30. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_explain_mixin.py +296 -296
  31. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_plotting_mixin.py +488 -548
  32. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/__init__.py +27 -27
  33. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_components.py +349 -342
  34. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_trainer.py +921 -913
  35. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_gnn.py +794 -785
  36. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_resn.py +454 -446
  37. ins_pricing/modelling/bayesopt/trainers/__init__.py +19 -0
  38. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_base.py +1294 -1282
  39. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_ft.py +64 -56
  40. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_glm.py +203 -198
  41. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_gnn.py +333 -325
  42. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_resn.py +279 -267
  43. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_xgb.py +515 -313
  44. ins_pricing/modelling/bayesopt/utils/__init__.py +67 -0
  45. ins_pricing/modelling/bayesopt/utils/constants.py +21 -0
  46. ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/distributed_utils.py +193 -186
  47. ins_pricing/modelling/bayesopt/utils/io_utils.py +7 -0
  48. ins_pricing/modelling/bayesopt/utils/losses.py +27 -0
  49. ins_pricing/modelling/bayesopt/utils/metrics_and_devices.py +17 -0
  50. ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/torch_trainer_mixin.py +636 -623
  51. ins_pricing/modelling/{core/evaluation.py → evaluation.py} +113 -104
  52. ins_pricing/modelling/explain/__init__.py +55 -55
  53. ins_pricing/modelling/explain/metrics.py +27 -174
  54. ins_pricing/modelling/explain/permutation.py +237 -237
  55. ins_pricing/modelling/plotting/__init__.py +40 -36
  56. ins_pricing/modelling/plotting/compat.py +228 -0
  57. ins_pricing/modelling/plotting/curves.py +572 -572
  58. ins_pricing/modelling/plotting/diagnostics.py +163 -163
  59. ins_pricing/modelling/plotting/geo.py +362 -362
  60. ins_pricing/modelling/plotting/importance.py +121 -121
  61. ins_pricing/pricing/__init__.py +27 -27
  62. ins_pricing/pricing/factors.py +67 -56
  63. ins_pricing/production/__init__.py +35 -25
  64. ins_pricing/production/{predict.py → inference.py} +140 -57
  65. ins_pricing/production/monitoring.py +8 -21
  66. ins_pricing/reporting/__init__.py +11 -11
  67. ins_pricing/setup.py +1 -1
  68. ins_pricing/tests/production/test_inference.py +90 -0
  69. ins_pricing/utils/__init__.py +112 -78
  70. ins_pricing/utils/device.py +258 -237
  71. ins_pricing/utils/features.py +53 -0
  72. ins_pricing/utils/io.py +72 -0
  73. ins_pricing/utils/logging.py +34 -1
  74. ins_pricing/{modelling/core/bayesopt/utils → utils}/losses.py +125 -129
  75. ins_pricing/utils/metrics.py +158 -24
  76. ins_pricing/utils/numerics.py +76 -0
  77. ins_pricing/utils/paths.py +9 -1
  78. ins_pricing/utils/profiling.py +8 -4
  79. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/METADATA +1 -1
  80. ins_pricing-0.5.1.dist-info/RECORD +132 -0
  81. ins_pricing/modelling/core/BayesOpt.py +0 -146
  82. ins_pricing/modelling/core/__init__.py +0 -1
  83. ins_pricing/modelling/core/bayesopt/trainers/__init__.py +0 -19
  84. ins_pricing/modelling/core/bayesopt/utils/__init__.py +0 -86
  85. ins_pricing/modelling/core/bayesopt/utils/constants.py +0 -183
  86. ins_pricing/modelling/core/bayesopt/utils/io_utils.py +0 -126
  87. ins_pricing/modelling/core/bayesopt/utils/metrics_and_devices.py +0 -555
  88. ins_pricing/modelling/core/bayesopt/utils.py +0 -105
  89. ins_pricing/modelling/core/bayesopt/utils_backup.py +0 -1503
  90. ins_pricing/tests/production/test_predict.py +0 -233
  91. ins_pricing-0.4.5.dist-info/RECORD +0 -130
  92. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/WHEEL +0 -0
  93. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/top_level.txt +0 -0
@@ -1,107 +1,107 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import Any, Callable, Dict, Optional
5
-
6
- import numpy as np
7
-
8
- from ...production.monitoring import (
9
- classification_metrics,
10
- regression_metrics,
11
- )
12
-
13
-
14
- @dataclass
15
- class CalibrationResult:
16
- method: str
17
- calibrator: Any
18
-
19
- def predict(self, scores: np.ndarray) -> np.ndarray:
20
- if self.method == "sigmoid":
21
- return self.calibrator.predict_proba(scores.reshape(-1, 1))[:, 1]
22
- return self.calibrator.transform(scores)
23
-
24
-
25
- def select_threshold(
26
- y_true: np.ndarray,
27
- y_pred: np.ndarray,
28
- *,
29
- metric: str = "f1",
30
- min_positive_rate: Optional[float] = None,
31
- grid: int = 99,
32
- ) -> Dict[str, float]:
33
- y_true = np.asarray(y_true, dtype=float).reshape(-1)
34
- y_pred = np.asarray(y_pred, dtype=float).reshape(-1)
35
- thresholds = np.linspace(0.01, 0.99, max(2, int(grid)))
36
- best = {"threshold": 0.5, "score": -1.0}
37
- for thr in thresholds:
38
- pred_label = (y_pred >= thr).astype(float)
39
- pos_rate = float(np.mean(pred_label))
40
- if min_positive_rate is not None and pos_rate < float(min_positive_rate):
41
- continue
42
- metrics = classification_metrics(y_true, y_pred, threshold=float(thr))
43
- precision = metrics.get("precision", 0.0)
44
- recall = metrics.get("recall", 0.0)
45
- f1 = 0.0 if (precision + recall) == 0 else 2 * precision * recall / (precision + recall)
46
- score = f1 if metric == "f1" else metrics.get(metric, f1)
47
- if score > best["score"]:
48
- best = {"threshold": float(thr), "score": float(score)}
49
- return best
50
-
51
-
52
- def calibrate_predictions(
53
- y_true: np.ndarray,
54
- y_pred: np.ndarray,
55
- *,
56
- method: str = "sigmoid",
57
- ) -> CalibrationResult:
58
- from sklearn.isotonic import IsotonicRegression
59
- from sklearn.linear_model import LogisticRegression
60
-
61
- y_true = np.asarray(y_true, dtype=float).reshape(-1)
62
- y_pred = np.asarray(y_pred, dtype=float).reshape(-1)
63
- method = str(method or "sigmoid").strip().lower()
64
- if method in {"platt", "sigmoid", "logistic"}:
65
- model = LogisticRegression(max_iter=200)
66
- model.fit(y_pred.reshape(-1, 1), y_true)
67
- return CalibrationResult(method="sigmoid", calibrator=model)
68
- if method in {"isotonic"}:
69
- model = IsotonicRegression(out_of_bounds="clip")
70
- model.fit(y_pred, y_true)
71
- return CalibrationResult(method="isotonic", calibrator=model)
72
- raise ValueError(f"Unsupported calibration method: {method}")
73
-
74
-
75
- def bootstrap_ci(
76
- metric_fn: Callable[[np.ndarray, np.ndarray, Optional[np.ndarray]], float],
77
- y_true: np.ndarray,
78
- y_pred: np.ndarray,
79
- *,
80
- weight: Optional[np.ndarray] = None,
81
- n_samples: int = 200,
82
- ci: float = 0.95,
83
- seed: Optional[int] = None,
84
- ) -> Dict[str, float]:
85
- rng = np.random.default_rng(seed)
86
- y_true = np.asarray(y_true).reshape(-1)
87
- y_pred = np.asarray(y_pred).reshape(-1)
88
- if weight is not None:
89
- weight = np.asarray(weight).reshape(-1)
90
- n = len(y_true)
91
- stats = []
92
- for _ in range(max(1, int(n_samples))):
93
- idx = rng.integers(0, n, size=n)
94
- y_t = y_true[idx]
95
- y_p = y_pred[idx]
96
- w_t = weight[idx] if weight is not None else None
97
- stats.append(float(metric_fn(y_t, y_p, w_t)))
98
- stats = np.asarray(stats, dtype=float)
99
- alpha = (1.0 - float(ci)) / 2.0
100
- low = float(np.quantile(stats, alpha))
101
- high = float(np.quantile(stats, 1.0 - alpha))
102
- return {"mean": float(np.mean(stats)), "low": low, "high": high}
103
-
104
-
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, Dict, Optional
5
+
6
+ import numpy as np
7
+
8
+ from ins_pricing.production.monitoring import (
9
+ classification_metrics,
10
+ regression_metrics,
11
+ )
12
+
13
+
14
+ @dataclass
15
+ class CalibrationResult:
16
+ method: str
17
+ calibrator: Any
18
+
19
+ def predict(self, scores: np.ndarray) -> np.ndarray:
20
+ if self.method == "sigmoid":
21
+ return self.calibrator.predict_proba(scores.reshape(-1, 1))[:, 1]
22
+ return self.calibrator.transform(scores)
23
+
24
+
25
+ def select_threshold(
26
+ y_true: np.ndarray,
27
+ y_pred: np.ndarray,
28
+ *,
29
+ metric: str = "f1",
30
+ min_positive_rate: Optional[float] = None,
31
+ grid: int = 99,
32
+ ) -> Dict[str, float]:
33
+ y_true = np.asarray(y_true, dtype=float).reshape(-1)
34
+ y_pred = np.asarray(y_pred, dtype=float).reshape(-1)
35
+ thresholds = np.linspace(0.01, 0.99, max(2, int(grid)))
36
+ best = {"threshold": 0.5, "score": -1.0}
37
+ for thr in thresholds:
38
+ pred_label = (y_pred >= thr).astype(float)
39
+ pos_rate = float(np.mean(pred_label))
40
+ if min_positive_rate is not None and pos_rate < float(min_positive_rate):
41
+ continue
42
+ metrics = classification_metrics(y_true, y_pred, threshold=float(thr))
43
+ precision = metrics.get("precision", 0.0)
44
+ recall = metrics.get("recall", 0.0)
45
+ f1 = 0.0 if (precision + recall) == 0 else 2 * precision * recall / (precision + recall)
46
+ score = f1 if metric == "f1" else metrics.get(metric, f1)
47
+ if score > best["score"]:
48
+ best = {"threshold": float(thr), "score": float(score)}
49
+ return best
50
+
51
+
52
+ def calibrate_predictions(
53
+ y_true: np.ndarray,
54
+ y_pred: np.ndarray,
55
+ *,
56
+ method: str = "sigmoid",
57
+ ) -> CalibrationResult:
58
+ from sklearn.isotonic import IsotonicRegression
59
+ from sklearn.linear_model import LogisticRegression
60
+
61
+ y_true = np.asarray(y_true, dtype=float).reshape(-1)
62
+ y_pred = np.asarray(y_pred, dtype=float).reshape(-1)
63
+ method = str(method or "sigmoid").strip().lower()
64
+ if method in {"platt", "sigmoid", "logistic"}:
65
+ model = LogisticRegression(max_iter=200)
66
+ model.fit(y_pred.reshape(-1, 1), y_true)
67
+ return CalibrationResult(method="sigmoid", calibrator=model)
68
+ if method in {"isotonic"}:
69
+ model = IsotonicRegression(out_of_bounds="clip")
70
+ model.fit(y_pred, y_true)
71
+ return CalibrationResult(method="isotonic", calibrator=model)
72
+ raise ValueError(f"Unsupported calibration method: {method}")
73
+
74
+
75
+ def bootstrap_ci(
76
+ metric_fn: Callable[[np.ndarray, np.ndarray, Optional[np.ndarray]], float],
77
+ y_true: np.ndarray,
78
+ y_pred: np.ndarray,
79
+ *,
80
+ weight: Optional[np.ndarray] = None,
81
+ n_samples: int = 200,
82
+ ci: float = 0.95,
83
+ seed: Optional[int] = None,
84
+ ) -> Dict[str, float]:
85
+ rng = np.random.default_rng(seed)
86
+ y_true = np.asarray(y_true).reshape(-1)
87
+ y_pred = np.asarray(y_pred).reshape(-1)
88
+ if weight is not None:
89
+ weight = np.asarray(weight).reshape(-1)
90
+ n = len(y_true)
91
+ stats = []
92
+ for _ in range(max(1, int(n_samples))):
93
+ idx = rng.integers(0, n, size=n)
94
+ y_t = y_true[idx]
95
+ y_p = y_pred[idx]
96
+ w_t = weight[idx] if weight is not None else None
97
+ stats.append(float(metric_fn(y_t, y_p, w_t)))
98
+ stats = np.asarray(stats, dtype=float)
99
+ alpha = (1.0 - float(ci)) / 2.0
100
+ low = float(np.quantile(stats, alpha))
101
+ high = float(np.quantile(stats, 1.0 - alpha))
102
+ return {"mean": float(np.mean(stats)), "low": low, "high": high}
103
+
104
+
105
105
  def metrics_report(
106
106
  y_true: np.ndarray,
107
107
  y_pred: np.ndarray,
@@ -113,3 +113,12 @@ def metrics_report(
113
113
  if str(task_type) == "classification":
114
114
  return classification_metrics(y_true, y_pred, threshold=threshold)
115
115
  return regression_metrics(y_true, y_pred, weight=weight)
116
+
117
+
118
+ __all__ = [
119
+ "CalibrationResult",
120
+ "select_threshold",
121
+ "calibrate_predictions",
122
+ "bootstrap_ci",
123
+ "metrics_report",
124
+ ]
@@ -1,55 +1,55 @@
1
- from __future__ import annotations
2
-
3
- from .gradients import (
4
- ft_integrated_gradients,
5
- gradient_x_input_torch,
6
- integrated_gradients_multi_input_torch,
7
- integrated_gradients_torch,
8
- resnet_integrated_gradients,
9
- summarize_attributions,
10
- )
11
- from .metrics import (
12
- auc_score,
13
- logloss,
14
- mae,
15
- mape,
16
- gamma_deviance,
17
- poisson_deviance,
18
- r2_score,
19
- rmse,
20
- tweedie_deviance,
21
- resolve_metric,
22
- )
23
- from .permutation import permutation_importance
24
- from .shap_utils import (
25
- compute_shap_core,
26
- compute_shap_ft,
27
- compute_shap_glm,
28
- compute_shap_resn,
29
- compute_shap_xgb,
30
- )
31
-
32
- __all__ = [
33
- "auc_score",
34
- "logloss",
35
- "mae",
36
- "mape",
37
- "gamma_deviance",
38
- "poisson_deviance",
39
- "tweedie_deviance",
40
- "r2_score",
41
- "rmse",
42
- "resolve_metric",
43
- "permutation_importance",
44
- "gradient_x_input_torch",
45
- "integrated_gradients_torch",
46
- "integrated_gradients_multi_input_torch",
47
- "summarize_attributions",
48
- "resnet_integrated_gradients",
49
- "ft_integrated_gradients",
50
- "compute_shap_core",
51
- "compute_shap_glm",
52
- "compute_shap_xgb",
53
- "compute_shap_resn",
54
- "compute_shap_ft",
55
- ]
1
+ from __future__ import annotations
2
+
3
+ from ins_pricing.modelling.explain.gradients import (
4
+ ft_integrated_gradients,
5
+ gradient_x_input_torch,
6
+ integrated_gradients_multi_input_torch,
7
+ integrated_gradients_torch,
8
+ resnet_integrated_gradients,
9
+ summarize_attributions,
10
+ )
11
+ from ins_pricing.modelling.explain.metrics import (
12
+ auc_score,
13
+ logloss,
14
+ mae,
15
+ mape,
16
+ gamma_deviance,
17
+ poisson_deviance,
18
+ r2_score,
19
+ rmse,
20
+ tweedie_deviance,
21
+ resolve_metric,
22
+ )
23
+ from ins_pricing.modelling.explain.permutation import permutation_importance
24
+ from ins_pricing.modelling.explain.shap_utils import (
25
+ compute_shap_core,
26
+ compute_shap_ft,
27
+ compute_shap_glm,
28
+ compute_shap_resn,
29
+ compute_shap_xgb,
30
+ )
31
+
32
+ __all__ = [
33
+ "auc_score",
34
+ "logloss",
35
+ "mae",
36
+ "mape",
37
+ "gamma_deviance",
38
+ "poisson_deviance",
39
+ "tweedie_deviance",
40
+ "r2_score",
41
+ "rmse",
42
+ "resolve_metric",
43
+ "permutation_importance",
44
+ "gradient_x_input_torch",
45
+ "integrated_gradients_torch",
46
+ "integrated_gradients_multi_input_torch",
47
+ "summarize_attributions",
48
+ "resnet_integrated_gradients",
49
+ "ft_integrated_gradients",
50
+ "compute_shap_core",
51
+ "compute_shap_glm",
52
+ "compute_shap_xgb",
53
+ "compute_shap_resn",
54
+ "compute_shap_ft",
55
+ ]
@@ -1,176 +1,29 @@
1
- from __future__ import annotations
2
-
3
- from typing import Callable, Optional, Tuple
4
-
5
- import numpy as np
6
-
7
- try:
8
- from sklearn.metrics import roc_auc_score
9
- except Exception: # pragma: no cover
10
- roc_auc_score = None
11
-
12
-
13
- def _to_numpy(arr) -> np.ndarray:
14
- out = np.asarray(arr, dtype=float)
15
- return out.reshape(-1)
16
-
17
-
18
- def _align(y_true, y_pred, sample_weight=None) -> Tuple[np.ndarray, np.ndarray, Optional[np.ndarray]]:
19
- y_t = _to_numpy(y_true)
20
- y_p = _to_numpy(y_pred)
21
- if y_t.shape[0] != y_p.shape[0]:
22
- raise ValueError("y_true and y_pred must have the same length.")
23
- if sample_weight is None:
24
- return y_t, y_p, None
25
- w = _to_numpy(sample_weight)
26
- if w.shape[0] != y_t.shape[0]:
27
- raise ValueError("sample_weight must have the same length as y_true.")
28
- return y_t, y_p, w
29
-
30
-
31
- def _weighted_mean(values: np.ndarray, weight: Optional[np.ndarray]) -> float:
32
- if weight is None:
33
- return float(np.mean(values))
34
- total = float(np.sum(weight))
35
- if total <= 0:
36
- return float(np.mean(values))
37
- return float(np.sum(values * weight) / total)
38
-
39
-
40
- def rmse(y_true, y_pred, sample_weight=None) -> float:
41
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
42
- err = (y_t - y_p) ** 2
43
- return float(np.sqrt(_weighted_mean(err, w)))
44
-
45
-
46
- def mae(y_true, y_pred, sample_weight=None) -> float:
47
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
48
- err = np.abs(y_t - y_p)
49
- return _weighted_mean(err, w)
50
-
51
-
52
- def mape(y_true, y_pred, sample_weight=None, eps: float = 1e-8) -> float:
53
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
54
- denom = np.maximum(np.abs(y_t), eps)
55
- err = np.abs((y_t - y_p) / denom)
56
- return _weighted_mean(err, w)
1
+ """Thin wrappers for shared metric utilities."""
57
2
 
3
+ from __future__ import annotations
58
4
 
59
- def r2_score(y_true, y_pred, sample_weight=None) -> float:
60
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
61
- if w is None:
62
- y_mean = float(np.mean(y_t))
63
- sse = float(np.sum((y_t - y_p) ** 2))
64
- sst = float(np.sum((y_t - y_mean) ** 2))
65
- else:
66
- w_sum = float(np.sum(w))
67
- if w_sum <= 0:
68
- y_mean = float(np.mean(y_t))
69
- else:
70
- y_mean = float(np.sum(w * y_t) / w_sum)
71
- sse = float(np.sum(w * (y_t - y_p) ** 2))
72
- sst = float(np.sum(w * (y_t - y_mean) ** 2))
73
- if sst <= 0:
74
- return 0.0
75
- return 1.0 - sse / sst
76
-
77
-
78
- def logloss(y_true, y_pred, sample_weight=None, eps: float = 1e-8) -> float:
79
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
80
- p = np.clip(y_p, eps, 1 - eps)
81
- loss = -(y_t * np.log(p) + (1 - y_t) * np.log(1 - p))
82
- return _weighted_mean(loss, w)
83
-
84
-
85
- def tweedie_deviance(
86
- y_true,
87
- y_pred,
88
- sample_weight=None,
89
- *,
90
- power: float = 1.5,
91
- eps: float = 1e-8,
92
- ) -> float:
93
- """Tweedie deviance (power=1 -> Poisson, power=2 -> Gamma, power=0 -> Normal)."""
94
- if power < 0:
95
- raise ValueError("power must be >= 0.")
96
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
97
- y_p = np.clip(y_p, eps, None)
98
- y_t_safe = np.clip(y_t, eps, None)
99
-
100
- if power == 0:
101
- dev = (y_t - y_p) ** 2
102
- elif power == 1:
103
- dev = 2 * (y_t_safe * np.log(y_t_safe / y_p) - (y_t_safe - y_p))
104
- elif power == 2:
105
- ratio = y_t_safe / y_p
106
- dev = 2 * ((ratio - 1) - np.log(ratio))
107
- else:
108
- term1 = np.power(y_t_safe, 2 - power) / ((1 - power) * (2 - power))
109
- term2 = y_t_safe * np.power(y_p, 1 - power) / (1 - power)
110
- term3 = np.power(y_p, 2 - power) / (2 - power)
111
- dev = 2 * (term1 - term2 + term3)
112
- return _weighted_mean(dev, w)
113
-
114
-
115
- def poisson_deviance(y_true, y_pred, sample_weight=None, eps: float = 1e-8) -> float:
116
- return tweedie_deviance(
117
- y_true,
118
- y_pred,
119
- sample_weight=sample_weight,
120
- power=1.0,
121
- eps=eps,
122
- )
123
-
124
-
125
- def gamma_deviance(y_true, y_pred, sample_weight=None, eps: float = 1e-8) -> float:
126
- return tweedie_deviance(
127
- y_true,
128
- y_pred,
129
- sample_weight=sample_weight,
130
- power=2.0,
131
- eps=eps,
132
- )
133
-
134
-
135
- def auc_score(y_true, y_pred, sample_weight=None) -> float:
136
- if roc_auc_score is None:
137
- raise RuntimeError("auc requires scikit-learn.")
138
- y_t, y_p, w = _align(y_true, y_pred, sample_weight)
139
- return float(roc_auc_score(y_t, y_p, sample_weight=w))
140
-
141
-
142
- def resolve_metric(
143
- metric: str | Callable,
144
- *,
145
- task_type: Optional[str] = None,
146
- higher_is_better: Optional[bool] = None,
147
- ) -> Tuple[Callable, bool, str]:
148
- if callable(metric):
149
- if higher_is_better is None:
150
- raise ValueError("higher_is_better must be provided for custom metric.")
151
- return metric, bool(higher_is_better), getattr(metric, "__name__", "custom")
152
-
153
- name = str(metric or "auto").lower()
154
- if name == "auto":
155
- if task_type == "classification":
156
- name = "logloss"
157
- else:
158
- name = "rmse"
159
-
160
- mapping = {
161
- "rmse": (rmse, False),
162
- "mae": (mae, False),
163
- "mape": (mape, False),
164
- "r2": (r2_score, True),
165
- "logloss": (logloss, False),
166
- "poisson": (poisson_deviance, False),
167
- "gamma": (gamma_deviance, False),
168
- "tweedie": (tweedie_deviance, False),
169
- "auc": (auc_score, True),
170
- }
171
- if name not in mapping:
172
- raise ValueError(f"Unsupported metric: {metric}")
173
- fn, hib = mapping[name]
174
- if higher_is_better is not None:
175
- hib = bool(higher_is_better)
176
- return fn, hib, name
5
+ from ins_pricing.utils.metrics import (
6
+ auc_score,
7
+ gamma_deviance,
8
+ logloss,
9
+ mae,
10
+ mape,
11
+ poisson_deviance,
12
+ r2_score,
13
+ resolve_metric,
14
+ rmse,
15
+ tweedie_deviance,
16
+ )
17
+
18
+ __all__ = [
19
+ "rmse",
20
+ "mae",
21
+ "mape",
22
+ "r2_score",
23
+ "logloss",
24
+ "tweedie_deviance",
25
+ "poisson_deviance",
26
+ "gamma_deviance",
27
+ "auc_score",
28
+ "resolve_metric",
29
+ ]