deskit 1.2.8__tar.gz → 1.3__tar.gz
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.
- {deskit-1.2.8/src/deskit.egg-info → deskit-1.3}/PKG-INFO +1 -1
- {deskit-1.2.8 → deskit-1.3}/pyproject.toml +1 -1
- {deskit-1.2.8 → deskit-1.3}/src/deskit/_config.py +27 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/dewsi.py +11 -7
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/dewsiv.py +15 -9
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/dewst.py +16 -9
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/dewsu.py +14 -9
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/dewsv.py +16 -9
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/knorae.py +14 -11
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/knoraiu.py +13 -10
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/knorau.py +13 -10
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/lwsei.py +1 -1
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/lwseu.py +1 -1
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/ola.py +1 -1
- deskit-1.3/src/deskit/norms.py +82 -0
- {deskit-1.2.8 → deskit-1.3/src/deskit.egg-info}/PKG-INFO +1 -1
- {deskit-1.2.8 → deskit-1.3}/src/deskit.egg-info/SOURCES.txt +1 -0
- {deskit-1.2.8 → deskit-1.3}/LICENSE +0 -0
- {deskit-1.2.8 → deskit-1.3}/README.md +0 -0
- {deskit-1.2.8 → deskit-1.3}/setup.cfg +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/__init__.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/base/__init__.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/base/base.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/base/knnbase.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/base/predictbase.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/des/__init__.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/metrics.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/neighbors.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/router.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit/utils.py +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit.egg-info/dependency_links.txt +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit.egg-info/requires.txt +0 -0
- {deskit-1.2.8 → deskit-1.3}/src/deskit.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ Not part of the public API.
|
|
|
4
4
|
"""
|
|
5
5
|
import numpy as np
|
|
6
6
|
from deskit.metrics import _METRICS, _PROB_METRICS, _SCALAR_METRICS
|
|
7
|
+
from deskit.norms import _NORMS
|
|
7
8
|
from deskit.utils import to_numpy
|
|
8
9
|
|
|
9
10
|
|
|
@@ -83,6 +84,32 @@ def resolve_metric(metric):
|
|
|
83
84
|
return name, _METRICS[name]
|
|
84
85
|
return None, metric
|
|
85
86
|
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Normalization resolution
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def resolve_norm(norm):
|
|
92
|
+
"""
|
|
93
|
+
Convert a normalization string or callable to (name_or_None, callable).
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
norm_name : str or None
|
|
98
|
+
String name if normalization was passed as a string; None for callables.
|
|
99
|
+
norm_fn : callable
|
|
100
|
+
The actual normalization function.
|
|
101
|
+
"""
|
|
102
|
+
if isinstance(norm, str):
|
|
103
|
+
name = norm.lower()
|
|
104
|
+
if name not in _NORMS:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"Unknown normalization '{norm}'. "
|
|
107
|
+
f"Built-in options: {sorted(_NORMS)}. "
|
|
108
|
+
f"Pass a callable for custom normalizations."
|
|
109
|
+
)
|
|
110
|
+
return name, _NORMS[name]
|
|
111
|
+
return None, norm
|
|
112
|
+
|
|
86
113
|
|
|
87
114
|
# ---------------------------------------------------------------------------
|
|
88
115
|
# Neighbor finder construction
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
DEWS-I: K-Nearest Neighbors with Distance-Weighted Softmax — Inverse-weighted.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -44,14 +44,17 @@ class DEWSI(KNNBase):
|
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
46
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
47
|
-
threshold=0.5, temperature=None, preset='balanced', distance_metric='
|
|
47
|
+
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
48
48
|
metric_name, metric_fn = resolve_metric(metric)
|
|
49
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
49
50
|
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
50
51
|
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, task=task)
|
|
51
52
|
self.task = task
|
|
52
53
|
self.threshold = threshold
|
|
53
54
|
self._temperature = temperature
|
|
54
55
|
self._metric_name = metric_name
|
|
56
|
+
self.norm = norm_fn
|
|
57
|
+
self.norm_name = norm_name
|
|
55
58
|
|
|
56
59
|
def fit(self, features, y, preds_dict):
|
|
57
60
|
"""
|
|
@@ -72,7 +75,7 @@ class DEWSI(KNNBase):
|
|
|
72
75
|
)
|
|
73
76
|
super().fit(features, y, preds_dict)
|
|
74
77
|
|
|
75
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
78
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
76
79
|
"""
|
|
77
80
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
78
81
|
Returns (batch, n_models) weight array.
|
|
@@ -87,6 +90,10 @@ class DEWSI(KNNBase):
|
|
|
87
90
|
self._temperature if self._temperature is not None else
|
|
88
91
|
(0.5 if self.mode == 'min' else 1.0))
|
|
89
92
|
th = threshold if threshold is not None else self.threshold
|
|
93
|
+
if normalization is not None:
|
|
94
|
+
_, norm_fn = resolve_norm(normalization)
|
|
95
|
+
else:
|
|
96
|
+
norm_fn = self.norm
|
|
90
97
|
|
|
91
98
|
distances, indices = self._kneighbors(x, k=k, loo=loo) # both (batch, k)
|
|
92
99
|
|
|
@@ -97,10 +104,7 @@ class DEWSI(KNNBase):
|
|
|
97
104
|
avg_scores = (neighbor_scores * inv_dist_w[:, :, np.newaxis]).sum(axis=1) # (batch, n_models)
|
|
98
105
|
|
|
99
106
|
# Normalize per neighborhood: best = 1.0, worst = 0.0
|
|
100
|
-
|
|
101
|
-
local_max = avg_scores.max(axis=1, keepdims=True)
|
|
102
|
-
local_range = local_max - local_min
|
|
103
|
-
norm_scores = (avg_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
|
|
107
|
+
norm_scores = norm_fn(avg_scores)
|
|
104
108
|
|
|
105
109
|
# Zero out models below threshold; fall back to single best if none pass
|
|
106
110
|
if th > 0:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
DEWS-IV: Distance-weighted Ensemble with Softmax — Inverse-distance + Variance-penalised.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -46,8 +46,9 @@ class DEWSIV(KNNBase):
|
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
48
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
49
|
-
threshold=0.5, temperature=None, preset='balanced', distance_metric='
|
|
49
|
+
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
50
50
|
metric_name, metric_fn = resolve_metric(metric)
|
|
51
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
51
52
|
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
52
53
|
|
|
53
54
|
self._use_signed = metric_name in _SIGNED_METRICS
|
|
@@ -59,6 +60,8 @@ class DEWSIV(KNNBase):
|
|
|
59
60
|
self.threshold = threshold
|
|
60
61
|
self._temperature = temperature
|
|
61
62
|
self._var_matrix = None # (n_val, n_models) signed residuals, MAE/MSE only
|
|
63
|
+
self.norm = norm_fn
|
|
64
|
+
self.norm_name = norm_name
|
|
62
65
|
|
|
63
66
|
def fit(self, features, y, preds_dict):
|
|
64
67
|
"""
|
|
@@ -87,7 +90,7 @@ class DEWSIV(KNNBase):
|
|
|
87
90
|
preds = np.asarray(preds_dict[name])
|
|
88
91
|
self._var_matrix[:, j] = np.vectorize(_signed_residual)(y, preds)
|
|
89
92
|
|
|
90
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
93
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
91
94
|
"""
|
|
92
95
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
93
96
|
Returns (batch, n_models) weight array.
|
|
@@ -96,6 +99,10 @@ class DEWSIV(KNNBase):
|
|
|
96
99
|
self._temperature if self._temperature is not None else
|
|
97
100
|
(0.5 if self.mode == 'min' else 1.0))
|
|
98
101
|
th = threshold if threshold is not None else self.threshold
|
|
102
|
+
if normalization is not None:
|
|
103
|
+
_, norm_fn = resolve_norm(normalization)
|
|
104
|
+
else:
|
|
105
|
+
norm_fn = self.norm
|
|
99
106
|
|
|
100
107
|
distances, indices = self._kneighbors(x, k=k, loo=loo) # both (batch, k)
|
|
101
108
|
|
|
@@ -128,12 +135,11 @@ class DEWSIV(KNNBase):
|
|
|
128
135
|
var_range = var_max - var_min
|
|
129
136
|
norm_var = (local_var - var_min) / np.where(var_range > 0, var_range, 1.0)
|
|
130
137
|
|
|
131
|
-
# Penalise inconsistent models
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
norm_scores = (norm_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
|
|
138
|
+
# Penalise inconsistent models
|
|
139
|
+
penalized = norm_scores / (1.0 + norm_var)
|
|
140
|
+
|
|
141
|
+
# Apply the user-selected normalization
|
|
142
|
+
norm_scores = norm_fn(penalized)
|
|
137
143
|
|
|
138
144
|
# Zero out models below threshold; fall back to single best if none pass
|
|
139
145
|
if th > 0:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
DEWS-T: Distance-weighted Ensemble with Softmax — Trend.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -50,9 +50,10 @@ class DEWST(KNNBase):
|
|
|
50
50
|
|
|
51
51
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
52
52
|
threshold=0.5, temperature=None, r2_threshold=0.7,
|
|
53
|
-
preset='balanced', distance_metric='euclidean', **kwargs):
|
|
53
|
+
preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
54
54
|
metric_name, metric_fn = resolve_metric(metric)
|
|
55
|
-
|
|
55
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
56
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
56
57
|
|
|
57
58
|
self._use_signed = metric_name in _SIGNED_METRICS
|
|
58
59
|
self._metric_name = metric_name
|
|
@@ -71,6 +72,9 @@ class DEWST(KNNBase):
|
|
|
71
72
|
self._temperature = temperature
|
|
72
73
|
self.r2_threshold = r2_threshold
|
|
73
74
|
|
|
75
|
+
self.norm = norm_fn
|
|
76
|
+
self.norm_name = norm_name
|
|
77
|
+
|
|
74
78
|
def fit(self, features, y, preds_dict):
|
|
75
79
|
"""
|
|
76
80
|
Fit the routing model on validation data.
|
|
@@ -89,7 +93,7 @@ class DEWST(KNNBase):
|
|
|
89
93
|
)
|
|
90
94
|
super().fit(features, y, preds_dict)
|
|
91
95
|
|
|
92
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, r2_threshold=None, loo=False):
|
|
96
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, r2_threshold=None, loo=False, normalization=None):
|
|
93
97
|
"""
|
|
94
98
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
95
99
|
Returns (batch, n_models) weight array.
|
|
@@ -99,6 +103,10 @@ class DEWST(KNNBase):
|
|
|
99
103
|
(0.5 if self._real_mode == 'min' else 1.0))
|
|
100
104
|
th = threshold if threshold is not None else self.threshold
|
|
101
105
|
r2_th = r2_threshold if r2_threshold is not None else self.r2_threshold
|
|
106
|
+
if normalization is not None:
|
|
107
|
+
_, norm_fn = resolve_norm(normalization)
|
|
108
|
+
else:
|
|
109
|
+
norm_fn = self.norm
|
|
102
110
|
|
|
103
111
|
distances, indices = self._kneighbors(x, k=k, loo=loo) # (batch, k)
|
|
104
112
|
k = distances.shape[1]
|
|
@@ -164,18 +172,17 @@ class DEWST(KNNBase):
|
|
|
164
172
|
use_trend = r2 >= r2_th
|
|
165
173
|
avg_scores = np.where(use_trend, trend_scores, dewsi_scores)
|
|
166
174
|
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
-
local_max = avg_scores.max(axis=1, keepdims=True)
|
|
170
|
-
local_range = local_max - local_min
|
|
171
|
-
norm_scores = (avg_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
|
|
175
|
+
# Normalize
|
|
176
|
+
norm_scores = norm_fn(avg_scores)
|
|
172
177
|
|
|
178
|
+
# Threshold
|
|
173
179
|
if th > 0:
|
|
174
180
|
gate = norm_scores >= th
|
|
175
181
|
any_pass = gate.any(axis=1, keepdims=True)
|
|
176
182
|
gate = np.where(any_pass, gate, norm_scores == 1.0)
|
|
177
183
|
norm_scores = norm_scores * gate
|
|
178
184
|
|
|
185
|
+
# Softmax
|
|
179
186
|
max_scores = norm_scores.max(axis=1, keepdims=True)
|
|
180
187
|
exp_scores = np.exp((norm_scores - max_scores) / t)
|
|
181
188
|
if th > 0:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
DEWS-U: K-Nearest Neighbors with Distance-Weighted Softmax.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -40,14 +40,17 @@ class DEWSU(KNNBase):
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
43
|
-
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
43
|
+
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
44
44
|
metric_name, metric_fn = resolve_metric(metric)
|
|
45
|
-
|
|
45
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
46
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
46
47
|
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, task=task)
|
|
47
48
|
self.task = task
|
|
48
49
|
self.threshold = threshold
|
|
49
50
|
self._temperature = temperature
|
|
50
51
|
self._metric_name = metric_name
|
|
52
|
+
self.norm = norm_fn
|
|
53
|
+
self.norm_name = norm_name
|
|
51
54
|
|
|
52
55
|
def fit(self, features, y, preds_dict):
|
|
53
56
|
"""
|
|
@@ -68,7 +71,7 @@ class DEWSU(KNNBase):
|
|
|
68
71
|
)
|
|
69
72
|
super().fit(features, y, preds_dict)
|
|
70
73
|
|
|
71
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
74
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
72
75
|
"""
|
|
73
76
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
74
77
|
Returns (batch, n_models) weight array.
|
|
@@ -77,6 +80,10 @@ class DEWSU(KNNBase):
|
|
|
77
80
|
self._temperature if self._temperature is not None else
|
|
78
81
|
(0.5 if self.mode == 'min' else 1.0))
|
|
79
82
|
th = threshold if threshold is not None else self.threshold
|
|
83
|
+
if normalization is not None:
|
|
84
|
+
_, norm_fn = resolve_norm(normalization)
|
|
85
|
+
else:
|
|
86
|
+
norm_fn = self.norm
|
|
80
87
|
|
|
81
88
|
_, indices = self._kneighbors(x, k=k, loo=loo) # (batch, k)
|
|
82
89
|
|
|
@@ -84,16 +91,14 @@ class DEWSU(KNNBase):
|
|
|
84
91
|
avg_scores = self.matrix[indices].mean(axis=1) # (batch, n_models)
|
|
85
92
|
|
|
86
93
|
# Normalize per neighborhood: best = 1.0, worst = 0.0
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
local_range = local_max - local_min
|
|
90
|
-
norm_scores = (avg_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
|
|
94
|
+
|
|
95
|
+
norm_scores = norm_fn(avg_scores)
|
|
91
96
|
|
|
92
97
|
# Zero out models below threshold; fall back to single best if none pass
|
|
93
98
|
if th > 0:
|
|
94
99
|
gate = norm_scores >= th
|
|
95
100
|
any_pass = gate.any(axis=1, keepdims=True)
|
|
96
|
-
gate = np.where(any_pass, gate, norm_scores == 1
|
|
101
|
+
gate = np.where(any_pass, gate, norm_scores == norm_scores.max(axis=1, keepdims=True))
|
|
97
102
|
norm_scores = norm_scores * gate
|
|
98
103
|
|
|
99
104
|
# Softmax
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
DEWS-V: Distance-weighted Ensemble with Softmax — Variance-penalised.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -46,8 +46,9 @@ class DEWSV(KNNBase):
|
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
48
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
49
|
-
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
49
|
+
threshold=0.5, temperature=None, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
50
50
|
metric_name, metric_fn = resolve_metric(metric)
|
|
51
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
51
52
|
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
52
53
|
|
|
53
54
|
self._use_signed = metric_name in _SIGNED_METRICS
|
|
@@ -60,6 +61,9 @@ class DEWSV(KNNBase):
|
|
|
60
61
|
self._temperature = temperature
|
|
61
62
|
self._var_matrix = None # (n_val, n_models) signed residuals, MAE/MSE only
|
|
62
63
|
|
|
64
|
+
self.norm = norm_fn
|
|
65
|
+
self.norm_name = norm_name
|
|
66
|
+
|
|
63
67
|
def fit(self, features, y, preds_dict):
|
|
64
68
|
"""
|
|
65
69
|
Fit the routing model on validation data.
|
|
@@ -87,7 +91,7 @@ class DEWSV(KNNBase):
|
|
|
87
91
|
preds = np.asarray(preds_dict[name])
|
|
88
92
|
self._var_matrix[:, j] = np.vectorize(_signed_residual)(y, preds)
|
|
89
93
|
|
|
90
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
94
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
91
95
|
"""
|
|
92
96
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
93
97
|
Returns (batch, n_models) weight array.
|
|
@@ -96,6 +100,10 @@ class DEWSV(KNNBase):
|
|
|
96
100
|
self._temperature if self._temperature is not None else
|
|
97
101
|
(0.5 if self.mode == 'min' else 1.0))
|
|
98
102
|
th = threshold if threshold is not None else self.threshold
|
|
103
|
+
if normalization is not None:
|
|
104
|
+
_, norm_fn = resolve_norm(normalization)
|
|
105
|
+
else:
|
|
106
|
+
norm_fn = self.norm
|
|
99
107
|
|
|
100
108
|
_, indices = self._kneighbors(x, k=k, loo=loo) # (batch, k)
|
|
101
109
|
|
|
@@ -119,12 +127,11 @@ class DEWSV(KNNBase):
|
|
|
119
127
|
var_range = var_max - var_min
|
|
120
128
|
norm_var = (local_var - var_min) / np.where(var_range > 0, var_range, 1.0)
|
|
121
129
|
|
|
122
|
-
# Penalise inconsistent models
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
norm_scores = (norm_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
|
|
130
|
+
# Penalise inconsistent models
|
|
131
|
+
penalized = norm_scores / (1.0 + norm_var)
|
|
132
|
+
|
|
133
|
+
# Apply the user-selected normalization
|
|
134
|
+
norm_scores = norm_fn(penalized)
|
|
128
135
|
|
|
129
136
|
# Zero out models below threshold; fall back to single best if none pass
|
|
130
137
|
if th > 0:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
KNORA-E: K-Nearest Oracles — Eliminate.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -35,14 +35,18 @@ class KNORAE(KNNBase):
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
38
|
-
threshold=0.5, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
38
|
+
threshold=0.5, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
39
39
|
metric_name, metric_fn = resolve_metric(metric)
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
41
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
42
|
+
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, normalization='bestrel', task=task)
|
|
42
43
|
self.task = task
|
|
43
44
|
self.threshold = threshold
|
|
44
45
|
self._metric_name = metric_name
|
|
45
46
|
|
|
47
|
+
self.norm = norm_fn
|
|
48
|
+
self.norm_name = norm_name
|
|
49
|
+
|
|
46
50
|
def fit(self, features, y, preds_dict):
|
|
47
51
|
"""
|
|
48
52
|
Fit the routing model on validation data.
|
|
@@ -58,13 +62,17 @@ class KNORAE(KNNBase):
|
|
|
58
62
|
)
|
|
59
63
|
super().fit(features, y, preds_dict)
|
|
60
64
|
|
|
61
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
65
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
62
66
|
"""
|
|
63
67
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
64
68
|
Returns (batch, n_models) weight array.
|
|
65
69
|
temperature is accepted for API compatibility but has no effect.
|
|
66
70
|
"""
|
|
67
71
|
th = threshold if threshold is not None else self.threshold
|
|
72
|
+
if normalization is not None:
|
|
73
|
+
_, norm_fn = resolve_norm(normalization)
|
|
74
|
+
else:
|
|
75
|
+
norm_fn = self.norm
|
|
68
76
|
n_models = len(self.models)
|
|
69
77
|
|
|
70
78
|
_, indices = self._kneighbors(x, k=k, loo=loo)
|
|
@@ -72,12 +80,7 @@ class KNORAE(KNNBase):
|
|
|
72
80
|
neighbor_scores = self.matrix[indices] # (batch, k, n_models)
|
|
73
81
|
|
|
74
82
|
# Normalize per neighbor: best model = 1.0, worst = 0.0
|
|
75
|
-
|
|
76
|
-
n_max = neighbor_scores.max(axis=2, keepdims=True)
|
|
77
|
-
n_range = n_max - n_min
|
|
78
|
-
norm = np.where(n_range > 0,
|
|
79
|
-
(neighbor_scores - n_min) / n_range,
|
|
80
|
-
1.0)
|
|
83
|
+
norm = norm_fn(neighbor_scores)
|
|
81
84
|
|
|
82
85
|
competent = norm >= th # (batch, k, n_models)
|
|
83
86
|
batch_size = x.shape[0]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
KNORA-IU: K-Nearest Oracles — Inverse-weighted Union.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -36,14 +36,18 @@ class KNORAIU(KNNBase):
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
39
|
-
threshold=0.5, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
39
|
+
threshold=0.5, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
40
40
|
metric_name, metric_fn = resolve_metric(metric)
|
|
41
|
-
|
|
41
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
42
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
42
43
|
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, task=task)
|
|
43
44
|
self.task = task
|
|
44
45
|
self.threshold = threshold
|
|
45
46
|
self._metric_name = metric_name
|
|
46
47
|
|
|
48
|
+
self.norm = norm_fn
|
|
49
|
+
self.norm_name = norm_name
|
|
50
|
+
|
|
47
51
|
def fit(self, features, y, preds_dict):
|
|
48
52
|
"""
|
|
49
53
|
Fit the routing model on validation data.
|
|
@@ -59,24 +63,23 @@ class KNORAIU(KNNBase):
|
|
|
59
63
|
)
|
|
60
64
|
super().fit(features, y, preds_dict)
|
|
61
65
|
|
|
62
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
66
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
63
67
|
"""
|
|
64
68
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
65
69
|
Returns (batch, n_models) weight array.
|
|
66
70
|
temperature is accepted for API compatibility but has no effect.
|
|
67
71
|
"""
|
|
68
72
|
th = threshold if threshold is not None else self.threshold
|
|
73
|
+
if normalization is not None:
|
|
74
|
+
_, norm_fn = resolve_norm(normalization)
|
|
75
|
+
else:
|
|
76
|
+
norm_fn = self.norm
|
|
69
77
|
|
|
70
78
|
distances, indices = self._kneighbors(x, k=k, loo=loo) # both (batch, k)
|
|
71
79
|
neighbor_scores = self.matrix[indices] # (batch, k, n_models)
|
|
72
80
|
|
|
73
81
|
# Normalize per neighbor: best model = 1.0, worst = 0.0
|
|
74
|
-
|
|
75
|
-
n_max = neighbor_scores.max(axis=2, keepdims=True)
|
|
76
|
-
n_range = n_max - n_min
|
|
77
|
-
norm = np.where(n_range > 0,
|
|
78
|
-
(neighbor_scores - n_min) / n_range,
|
|
79
|
-
1.0)
|
|
82
|
+
norm = norm_fn(neighbor_scores)
|
|
80
83
|
|
|
81
84
|
competent = norm >= th # (batch, k, n_models)
|
|
82
85
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
KNORA-U: K-Nearest Oracles — Union.
|
|
3
3
|
"""
|
|
4
4
|
from deskit.base.knnbase import KNNBase
|
|
5
|
-
from deskit._config import make_finder, resolve_metric, prep_fit_inputs
|
|
5
|
+
from deskit._config import make_finder, resolve_metric, resolve_norm, prep_fit_inputs
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
@@ -36,14 +36,18 @@ class KNORAU(KNNBase):
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
39
|
-
threshold=0.5, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
39
|
+
threshold=0.5, preset='balanced', distance_metric='euclidean', normalization='bestrel', **kwargs):
|
|
40
40
|
metric_name, metric_fn = resolve_metric(metric)
|
|
41
|
-
|
|
41
|
+
norm_name, norm_fn = resolve_norm(normalization)
|
|
42
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
42
43
|
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, task=task)
|
|
43
44
|
self.task = task
|
|
44
45
|
self.threshold = threshold
|
|
45
46
|
self._metric_name = metric_name
|
|
46
47
|
|
|
48
|
+
self.norm = norm_fn
|
|
49
|
+
self.norm_name = norm_name
|
|
50
|
+
|
|
47
51
|
def fit(self, features, y, preds_dict):
|
|
48
52
|
"""
|
|
49
53
|
Fit the routing model on validation data.
|
|
@@ -59,24 +63,23 @@ class KNORAU(KNNBase):
|
|
|
59
63
|
)
|
|
60
64
|
super().fit(features, y, preds_dict)
|
|
61
65
|
|
|
62
|
-
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False):
|
|
66
|
+
def _weights_batch(self, x, temperature=None, threshold=None, k=None, loo=False, normalization=None):
|
|
63
67
|
"""
|
|
64
68
|
Core weight computation. x is a 2-D float64 numpy array (batch, n_features).
|
|
65
69
|
Returns (batch, n_models) weight array.
|
|
66
70
|
temperature is accepted for API compatibility but has no effect.
|
|
67
71
|
"""
|
|
68
72
|
th = threshold if threshold is not None else self.threshold
|
|
73
|
+
if normalization is not None:
|
|
74
|
+
_, norm_fn = resolve_norm(normalization)
|
|
75
|
+
else:
|
|
76
|
+
norm_fn = self.norm
|
|
69
77
|
|
|
70
78
|
_, indices = self._kneighbors(x, k=k, loo=loo)
|
|
71
79
|
neighbor_scores = self.matrix[indices] # (batch, k, n_models)
|
|
72
80
|
|
|
73
81
|
# Normalize per neighbor: best model = 1.0, worst = 0.0
|
|
74
|
-
|
|
75
|
-
n_max = neighbor_scores.max(axis=2, keepdims=True)
|
|
76
|
-
n_range = n_max - n_min
|
|
77
|
-
norm = np.where(n_range > 0,
|
|
78
|
-
(neighbor_scores - n_min) / n_range,
|
|
79
|
-
1.0) # tied → all equally competent
|
|
82
|
+
norm = norm_fn(neighbor_scores)
|
|
80
83
|
|
|
81
84
|
# votes[b, j] = number of neighbours where model j exceeds the threshold
|
|
82
85
|
votes = (norm >= th).sum(axis=1).astype(float) # (batch, n_models)
|
|
@@ -30,7 +30,7 @@ class LWSEI(PredictBase):
|
|
|
30
30
|
def __init__(self, task, k=10, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
31
31
|
self.task = task
|
|
32
32
|
self.k = k
|
|
33
|
-
self._finder = make_finder(preset, k, **kwargs)
|
|
33
|
+
self._finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
34
34
|
self.models = None
|
|
35
35
|
|
|
36
36
|
self._val_preds = None # (n_val, n_models) or (n_val, n_models, n_classes)
|
|
@@ -30,7 +30,7 @@ class LWSEU(PredictBase):
|
|
|
30
30
|
def __init__(self, task, k=10, preset='balanced', distance_metric='euclidean', **kwargs):
|
|
31
31
|
self.task = task
|
|
32
32
|
self.k = k
|
|
33
|
-
self._finder = make_finder(preset, k, **kwargs)
|
|
33
|
+
self._finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
34
34
|
self.models = None
|
|
35
35
|
|
|
36
36
|
self._val_preds = None # (n_val, n_models) or (n_val, n_models, n_classes)
|
|
@@ -33,7 +33,7 @@ class OLA(KNNBase):
|
|
|
33
33
|
def __init__(self, task, metric='mae', mode='min', k=10,
|
|
34
34
|
preset='balanced', threshold=None, distance_metric='euclidean', **kwargs):
|
|
35
35
|
metric_name, metric_fn = resolve_metric(metric)
|
|
36
|
-
finder = make_finder(preset, k, **kwargs)
|
|
36
|
+
finder = make_finder(preset, k, distance_metric=distance_metric, **kwargs)
|
|
37
37
|
super().__init__(metric=metric_fn, mode=mode, neighbor_finder=finder, task=task)
|
|
38
38
|
self.task = task
|
|
39
39
|
self._metric_name = metric_name
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Normalization functions for Dynamic Ensemble Selection.
|
|
3
|
+
|
|
4
|
+
All functions operate row-wise: each row corresponds to a query instance,
|
|
5
|
+
and columns are the base models. They return a matrix of the same shape,
|
|
6
|
+
with values in a suitable range (usually [0,1]) and "higher = better".
|
|
7
|
+
|
|
8
|
+
Built‑in options:
|
|
9
|
+
'minmax' : classic min‑max scaling per row → [0,1]
|
|
10
|
+
'zscore' : z‑score per row (mean 0, std 1) – may be negative
|
|
11
|
+
'bestrel' : ratio‑to‑best, automatically handles sign -> [0,1]:
|
|
12
|
+
- if scores are all negative (e.g., -errors): best / score
|
|
13
|
+
- if scores are all non‑negative (e.g., V/IV): score / best
|
|
14
|
+
'softmax' : softmax with temperature=1 (optional)
|
|
15
|
+
'rank' : rank‑based (0 for worst, 1 for best) – robust to outliers
|
|
16
|
+
|
|
17
|
+
Custom functions can be passed via the `normalization` parameter in DEWS.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
EPS = 1e-12
|
|
23
|
+
|
|
24
|
+
def minmax(x):
|
|
25
|
+
"""Row‑wise min‑max scaling to [0,1]."""
|
|
26
|
+
x_min = x.min(axis=1, keepdims=True)
|
|
27
|
+
x_max = x.max(axis=1, keepdims=True)
|
|
28
|
+
x_range = x_max - x_min
|
|
29
|
+
# Avoid division by zero (if range is 0, set to 1.0)
|
|
30
|
+
safe_range = np.where(x_range > 0, x_range, 1.0)
|
|
31
|
+
return (x - x_min) / safe_range
|
|
32
|
+
|
|
33
|
+
def zscore(x):
|
|
34
|
+
"""Row‑wise z‑score (standard score) with bias‑corrected std."""
|
|
35
|
+
mean = x.mean(axis=1, keepdims=True)
|
|
36
|
+
std = x.std(axis=1, keepdims=True, ddof=1) # sample std
|
|
37
|
+
safe_std = np.where(std > EPS, std, 1.0)
|
|
38
|
+
return (x - mean) / safe_std
|
|
39
|
+
|
|
40
|
+
def bestrel(x):
|
|
41
|
+
"""
|
|
42
|
+
Ratio‑to‑best normalization.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
max_val = x.max(axis=1, keepdims=True)
|
|
46
|
+
# Determine per‑row which normalization to use based on the max
|
|
47
|
+
eps = 1e-12
|
|
48
|
+
safe_x = np.minimum(x, -eps)
|
|
49
|
+
norm_neg = max_val / safe_x # this will be <=1 because max is the least negative
|
|
50
|
+
safe_max = np.where(max_val > eps, max_val, eps)
|
|
51
|
+
norm_pos = x / safe_max # will be in [0,1]
|
|
52
|
+
|
|
53
|
+
neg_mask = max_val < -eps
|
|
54
|
+
result = np.where(neg_mask, norm_neg, norm_pos)
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
def softmax(x, temperature=1.0):
|
|
58
|
+
"""Row‑wise softmax with optional temperature (higher temperature → more uniform)."""
|
|
59
|
+
x_scaled = x / temperature
|
|
60
|
+
x_shifted = x_scaled - x_scaled.max(axis=1, keepdims=True) # stability
|
|
61
|
+
exp_x = np.exp(x_shifted)
|
|
62
|
+
return exp_x / exp_x.sum(axis=1, keepdims=True)
|
|
63
|
+
|
|
64
|
+
def rank_norm(x):
|
|
65
|
+
"""
|
|
66
|
+
Convert each row to fractional ranks: worst → 0, best → 1.
|
|
67
|
+
Ties are averaged.
|
|
68
|
+
"""
|
|
69
|
+
# Get ranks along axis=1 (method='average' gives average rank for ties)
|
|
70
|
+
ranks = x.argsort(axis=1).argsort(axis=1) + 1 # ranks from 1 to M
|
|
71
|
+
# Convert to [0,1] with 1 for best (highest original value)
|
|
72
|
+
M = x.shape[1]
|
|
73
|
+
# For 'higher is better', we invert: rank 1 (lowest original) gets 0, rank M (highest) gets 1
|
|
74
|
+
return (ranks - 1) / (M - 1) if M > 1 else np.ones_like(x) * 0.5
|
|
75
|
+
|
|
76
|
+
_NORMS = {
|
|
77
|
+
'minmax': minmax,
|
|
78
|
+
'z': zscore,
|
|
79
|
+
'bestrel': bestrel,
|
|
80
|
+
'softmax': softmax,
|
|
81
|
+
'rank_norm': rank_norm
|
|
82
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|