ins-pricing 0.4.5__py3-none-any.whl → 0.5.0__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 (84) hide show
  1. ins_pricing/README.md +48 -22
  2. ins_pricing/__init__.py +142 -90
  3. ins_pricing/cli/BayesOpt_entry.py +52 -50
  4. ins_pricing/cli/BayesOpt_incremental.py +39 -105
  5. ins_pricing/cli/Explain_Run.py +31 -23
  6. ins_pricing/cli/Explain_entry.py +532 -579
  7. ins_pricing/cli/Pricing_Run.py +31 -23
  8. ins_pricing/cli/bayesopt_entry_runner.py +11 -9
  9. ins_pricing/cli/utils/cli_common.py +256 -256
  10. ins_pricing/cli/utils/cli_config.py +375 -375
  11. ins_pricing/cli/utils/import_resolver.py +382 -365
  12. ins_pricing/cli/utils/notebook_utils.py +340 -340
  13. ins_pricing/cli/watchdog_run.py +209 -201
  14. ins_pricing/frontend/__init__.py +10 -10
  15. ins_pricing/frontend/example_workflows.py +1 -1
  16. ins_pricing/governance/__init__.py +20 -20
  17. ins_pricing/governance/release.py +159 -159
  18. ins_pricing/modelling/__init__.py +147 -92
  19. ins_pricing/modelling/{core/bayesopt → bayesopt}/README.md +2 -2
  20. ins_pricing/modelling/{core/bayesopt → bayesopt}/__init__.py +64 -102
  21. ins_pricing/modelling/{core/bayesopt → bayesopt}/config_preprocess.py +562 -562
  22. ins_pricing/modelling/{core/bayesopt → bayesopt}/core.py +965 -964
  23. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_explain_mixin.py +296 -296
  24. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_plotting_mixin.py +482 -548
  25. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/__init__.py +27 -27
  26. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_trainer.py +915 -913
  27. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_gnn.py +788 -785
  28. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_resn.py +448 -446
  29. ins_pricing/modelling/bayesopt/trainers/__init__.py +19 -0
  30. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_base.py +1308 -1308
  31. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_ft.py +3 -3
  32. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_glm.py +197 -198
  33. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_gnn.py +344 -344
  34. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_resn.py +283 -283
  35. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_xgb.py +346 -347
  36. ins_pricing/modelling/bayesopt/utils/__init__.py +67 -0
  37. ins_pricing/modelling/bayesopt/utils/constants.py +21 -0
  38. ins_pricing/modelling/bayesopt/utils/io_utils.py +7 -0
  39. ins_pricing/modelling/bayesopt/utils/losses.py +27 -0
  40. ins_pricing/modelling/bayesopt/utils/metrics_and_devices.py +17 -0
  41. ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/torch_trainer_mixin.py +623 -623
  42. ins_pricing/modelling/{core/evaluation.py → evaluation.py} +113 -104
  43. ins_pricing/modelling/explain/__init__.py +55 -55
  44. ins_pricing/modelling/explain/metrics.py +27 -174
  45. ins_pricing/modelling/explain/permutation.py +237 -237
  46. ins_pricing/modelling/plotting/__init__.py +40 -36
  47. ins_pricing/modelling/plotting/compat.py +228 -0
  48. ins_pricing/modelling/plotting/curves.py +572 -572
  49. ins_pricing/modelling/plotting/diagnostics.py +163 -163
  50. ins_pricing/modelling/plotting/geo.py +362 -362
  51. ins_pricing/modelling/plotting/importance.py +121 -121
  52. ins_pricing/pricing/__init__.py +27 -27
  53. ins_pricing/production/__init__.py +35 -25
  54. ins_pricing/production/{predict.py → inference.py} +140 -57
  55. ins_pricing/production/monitoring.py +8 -21
  56. ins_pricing/reporting/__init__.py +11 -11
  57. ins_pricing/setup.py +1 -1
  58. ins_pricing/tests/production/test_inference.py +90 -0
  59. ins_pricing/utils/__init__.py +116 -83
  60. ins_pricing/utils/device.py +255 -255
  61. ins_pricing/utils/features.py +53 -0
  62. ins_pricing/utils/io.py +72 -0
  63. ins_pricing/{modelling/core/bayesopt/utils → utils}/losses.py +125 -129
  64. ins_pricing/utils/metrics.py +158 -24
  65. ins_pricing/utils/numerics.py +76 -0
  66. ins_pricing/utils/paths.py +9 -1
  67. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.0.dist-info}/METADATA +182 -182
  68. ins_pricing-0.5.0.dist-info/RECORD +131 -0
  69. ins_pricing/modelling/core/BayesOpt.py +0 -146
  70. ins_pricing/modelling/core/__init__.py +0 -1
  71. ins_pricing/modelling/core/bayesopt/trainers/__init__.py +0 -19
  72. ins_pricing/modelling/core/bayesopt/utils/__init__.py +0 -86
  73. ins_pricing/modelling/core/bayesopt/utils/constants.py +0 -183
  74. ins_pricing/modelling/core/bayesopt/utils/io_utils.py +0 -126
  75. ins_pricing/modelling/core/bayesopt/utils/metrics_and_devices.py +0 -555
  76. ins_pricing/modelling/core/bayesopt/utils.py +0 -105
  77. ins_pricing/modelling/core/bayesopt/utils_backup.py +0 -1503
  78. ins_pricing/tests/production/test_predict.py +0 -233
  79. ins_pricing-0.4.5.dist-info/RECORD +0 -130
  80. /ins_pricing/modelling/{core/bayesopt → bayesopt}/config_components.py +0 -0
  81. /ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_components.py +0 -0
  82. /ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/distributed_utils.py +0 -0
  83. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.0.dist-info}/WHEEL +0 -0
  84. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.0.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
+ ]