deskit 0.2.0__tar.gz → 0.4.0__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.
Files changed (28) hide show
  1. {deskit-0.2.0/src/deskit.egg-info → deskit-0.4.0}/PKG-INFO +37 -29
  2. {deskit-0.2.0 → deskit-0.4.0}/README.md +36 -28
  3. {deskit-0.2.0 → deskit-0.4.0}/pyproject.toml +1 -1
  4. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/__init__.py +4 -4
  5. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/des/__init__.py +2 -2
  6. deskit-0.2.0/src/deskit/des/knndwsi.py → deskit-0.4.0/src/deskit/des/dewsi.py +4 -4
  7. deskit-0.4.0/src/deskit/des/dewst.py +200 -0
  8. deskit-0.2.0/src/deskit/des/knndws.py → deskit-0.4.0/src/deskit/des/dewsu.py +3 -3
  9. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/router.py +9 -9
  10. {deskit-0.2.0 → deskit-0.4.0/src/deskit.egg-info}/PKG-INFO +37 -29
  11. {deskit-0.2.0 → deskit-0.4.0}/src/deskit.egg-info/SOURCES.txt +3 -2
  12. {deskit-0.2.0 → deskit-0.4.0}/LICENSE +0 -0
  13. {deskit-0.2.0 → deskit-0.4.0}/setup.cfg +0 -0
  14. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/_config.py +0 -0
  15. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/analysis.py +0 -0
  16. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/base/__init__.py +0 -0
  17. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/base/base.py +0 -0
  18. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/base/knnbase.py +0 -0
  19. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/des/knorae.py +0 -0
  20. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/des/knoraiu.py +0 -0
  21. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/des/knorau.py +0 -0
  22. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/des/ola.py +0 -0
  23. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/metrics.py +0 -0
  24. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/neighbors.py +0 -0
  25. {deskit-0.2.0 → deskit-0.4.0}/src/deskit/utils.py +0 -0
  26. {deskit-0.2.0 → deskit-0.4.0}/src/deskit.egg-info/dependency_links.txt +0 -0
  27. {deskit-0.2.0 → deskit-0.4.0}/src/deskit.egg-info/requires.txt +0 -0
  28. {deskit-0.2.0 → deskit-0.4.0}/src/deskit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deskit
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: A Python library for Dynamic Ensemble Selection
5
5
  Author: Tikhon Vodyanov
6
6
  License-Expression: MIT
@@ -31,7 +31,7 @@ Dynamic: license-file
31
31
 
32
32
  # deskit
33
33
 
34
- [deskit](https://TikaaVo.github.io/deskit/) is a flexible, lightweight, and easy-to-use ensembling library that implements
34
+ deskit is a flexible, lightweight, and easy-to-use ensembling library that implements
35
35
  Dynamic Ensemble Selection (DES) algorithms for ensembling multiple ML models
36
36
  on a given dataset.
37
37
 
@@ -43,6 +43,8 @@ requiring any wrappers, including custom models, popular ML libraries, and APIs.
43
43
  deskit includes several DES algorithms, and it works with both classification
44
44
  and regression.
45
45
 
46
+ See the full documentation [here](https://TikaaVo.github.io/deskit/).
47
+
46
48
  # Dynamic Ensemble Selection
47
49
 
48
50
  Ensemble learning in machine learning refers to when multiple models trained on a
@@ -148,14 +150,15 @@ weights = router.predict(X_test[i])
148
150
 
149
151
  ## Algorithms
150
152
 
151
- | Method | Best for | Notes |
152
- |-----------|---|----------------------------------------------------------------------------------------------------------|
153
- | `KNNDWS` | Regression | Softmax over neighbourhood-averaged scores. Temperature controls sharpness. |
154
- | `KNNDWSI` | Regression | Like KNN-DWS but scores are inverse-distance weighted. |
155
- | `KNORAU` | Classification | Vote-count weighting. Each model earns one vote per neighbour it correctly classifies. |
156
- | `KNORAE` | Classification | Intersection-based. Only models correct on all neighbours survive; falls back to smaller neighbourhoods. |
157
- | `KNORAIU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
158
- | `OLA` | Both | Hard selection: only the single best model in the neighbourhood contributes. |
153
+ | Method | Best for | Notes |
154
+ |------------|----------------|------------------------------------------------------------------------------------------------------|
155
+ | `DEWS-U` | Regression | Softmax over neighborhood-averaged scores. Temperature controls sharpness. |
156
+ | `DEWS-I` | Regression | Like DEWS-U but scores are inverse-distance weighted. |
157
+ | `DEWS-T` | Both | Like DEWS-U but fits a weighted trend line over neighbor scores and extrapolates to the test point. |
158
+ | `KNORA-U` | Classification | Vote-count weighting. Each model earns one vote per neighbor it correctly classifies. |
159
+ | `KNORA-E` | Classification | Intersection-based. Only models correct on all neighbors survive; falls back to smaller neighborhoods. |
160
+ | `KNORA-IU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
161
+ | `OLA` | Both | Hard selection: only the single best model in the neighborhood contributes. |
159
162
 
160
163
  ---
161
164
 
@@ -202,13 +205,18 @@ def pinball(y_true, y_pred, alpha=0.9):
202
205
  e = y_true - y_pred
203
206
  return alpha * e if e >= 0 else (alpha - 1) * e
204
207
 
205
- router = KNNDWS(task="regression", metric=pinball, mode="min", k=20)
208
+ router = DEWSU(task="regression", metric=pinball, mode="min", k=20)
206
209
  ```
207
210
 
208
211
  Built-in metric strings: `accuracy`, `mae`, `mse`, `rmse`, `log_loss`, `prob_correct`.
209
212
 
210
213
  ---
211
214
 
215
+ ## Data types
216
+
217
+ deskit can be used with non-tabular data types like images, time series, and more. However, when used, the
218
+ passed features either need to be run through a feature extractor beforehand, such as a CNN backbone for images.
219
+
212
220
  ## Benchmark results
213
221
 
214
222
  100-seed benchmark (seeds 0–99) on standard sklearn and OpenML datasets. "Best Single" is the best
@@ -224,39 +232,39 @@ Pool: KNN, Decision Tree, SVR, Ridge, Bayesian Ridge.
224
232
 
225
233
  This pool was selected for having variability in architectures while avoiding a single dominant model.
226
234
 
227
- deskit algorithms tested: OLA, KNN-DWS, KNN-DWS-I, KNORA-U, KNORA-E, KNORA-IU.
235
+ deskit algorithms tested: OLA, DEWS-U, DEWS-I, DEWS-T, KNORA-U, KNORA-E, KNORA-IU.
228
236
 
229
237
  ### Regression (MAE, lower is better)
230
238
 
231
- % shown as delta vs Best Single. 100-seed mean.
239
+ % shown as delta vs Best Single. 20-seed mean.
232
240
 
233
- | Dataset | Best Single | Simple Avg | deskit best |
234
- |------------------------------|-------------|------------|-------------------------|
235
- | California Housing (sklearn) | 0.3955 | +7.93% | **−2.68%** (KNN-DWS-I) |
236
- | Bike Sharing (OpenML) | 51.604 | +48.39% | **−6.25%** (KNN-DWS-I) |
237
- | Abalone (OpenML) | **1.4923** | +1.29% | +1.61% (KNORA-IU) |
238
- | Diabetes (sklearn) | **44.986** | +2.98% | +0.88% (KNN-DWS-I) |
239
- | Concrete Strength (OpenML) | 5.3934 | +21.30% | **−2.85%** (KNORA-IU) |
241
+ | Dataset | Best Single | Simple Avg | deskit best |
242
+ |------------------------------|-------------|------------|---------------------------|
243
+ | California Housing (sklearn) | 0.3956 | +7.99% | **−2.54%** (DEWS-I) |
244
+ | Bike Sharing (OpenML) | 51.678 | +47.77% | **−6.86%** (DEWS-I) |
245
+ | Abalone (OpenML) | **1.4981** | +1.14% | +1.47% (KNORA-U/KNORA-IU) |
246
+ | Diabetes (sklearn) | **44.504** | +3.18% | +1.09% (DEWS-I/DEWS-T) |
247
+ | Concrete Strength (OpenML) | 5.2686 | +23.66% | **−1.20%** (DEWS-I) |
240
248
 
241
249
  deskit beats best single and simple averaging on 3/5 regression datasets. This shows how DES can provide a
242
250
  strong boost if used on the right dataset, but it might be counterproductive if used blindly.
243
251
 
244
252
  KNORA variants are designed for classification, which explains the poor performance
245
253
  on regression datasets; However, some exception can occur in certain datasets, either where
246
- feature space is has hard clusters (like in Concrete Strength) or when the target is discrete
254
+ feature space has hard clusters (like in Concrete Strength) or when the target is discrete
247
255
  and classification-like (like in Abalone).
248
256
 
249
257
  ### Classification (Accuracy, higher is better)
250
258
 
251
- % shown as delta vs Best Single. 100-seed mean.
259
+ % shown as delta vs Best Single. 20-seed mean.
252
260
 
253
- | Dataset | Best Single | Simple Avg | deskit best |
254
- |------------------------|-------------|------------|-------------------------|
255
- | HAR (OpenML) | 98.24% | −0.32% | **+0.14%** (KNN-DWS-I) |
256
- | Yeast (OpenML) | 59.19% | +0.46% | **+1.48%** (KNORA-IU) |
257
- | Image Segment (OpenML) | 93.65% | +1.70% | **+2.33%** (KNORA-IU) |
258
- | Waveform (OpenML) | **86.28%** | −1.04% | −0.55% (KNN-DWS-I) |
259
- | Vowel (OpenML) | 90.54% | −1.81% | **+0.93%** (KNORA-IU) |
261
+ | Dataset | Best Single | Simple Avg | deskit best |
262
+ |------------------------|-------------|------------|--------------------------|
263
+ | HAR (OpenML) | 98.24% | −0.33% | **+0.16%** (DEWS-T) |
264
+ | Yeast (OpenML) | 58.87% | +0.77% | **+1.66%** (KNORA-IU) |
265
+ | Image Segment (OpenML) | 93.70% | +1.40% | **+2.25%** (DEWS-T) |
266
+ | Waveform (OpenML) | **85.91%** | −0.98% | −0.39% (DEWS-T) |
267
+ | Vowel (OpenML) | 89.95% | −2.05% | **+0.93%** (KNORA-IU) |
260
268
 
261
269
  deskit beats or matches best single and simple averaging on 4/5 classification datasets. As seen on regression, DES
262
270
  can improve or hurt performance, so it must be used wisely, but if used correctly it can show promising results.
@@ -1,6 +1,6 @@
1
1
  # deskit
2
2
 
3
- [deskit](https://TikaaVo.github.io/deskit/) is a flexible, lightweight, and easy-to-use ensembling library that implements
3
+ deskit is a flexible, lightweight, and easy-to-use ensembling library that implements
4
4
  Dynamic Ensemble Selection (DES) algorithms for ensembling multiple ML models
5
5
  on a given dataset.
6
6
 
@@ -12,6 +12,8 @@ requiring any wrappers, including custom models, popular ML libraries, and APIs.
12
12
  deskit includes several DES algorithms, and it works with both classification
13
13
  and regression.
14
14
 
15
+ See the full documentation [here](https://TikaaVo.github.io/deskit/).
16
+
15
17
  # Dynamic Ensemble Selection
16
18
 
17
19
  Ensemble learning in machine learning refers to when multiple models trained on a
@@ -117,14 +119,15 @@ weights = router.predict(X_test[i])
117
119
 
118
120
  ## Algorithms
119
121
 
120
- | Method | Best for | Notes |
121
- |-----------|---|----------------------------------------------------------------------------------------------------------|
122
- | `KNNDWS` | Regression | Softmax over neighbourhood-averaged scores. Temperature controls sharpness. |
123
- | `KNNDWSI` | Regression | Like KNN-DWS but scores are inverse-distance weighted. |
124
- | `KNORAU` | Classification | Vote-count weighting. Each model earns one vote per neighbour it correctly classifies. |
125
- | `KNORAE` | Classification | Intersection-based. Only models correct on all neighbours survive; falls back to smaller neighbourhoods. |
126
- | `KNORAIU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
127
- | `OLA` | Both | Hard selection: only the single best model in the neighbourhood contributes. |
122
+ | Method | Best for | Notes |
123
+ |------------|----------------|------------------------------------------------------------------------------------------------------|
124
+ | `DEWS-U` | Regression | Softmax over neighborhood-averaged scores. Temperature controls sharpness. |
125
+ | `DEWS-I` | Regression | Like DEWS-U but scores are inverse-distance weighted. |
126
+ | `DEWS-T` | Both | Like DEWS-U but fits a weighted trend line over neighbor scores and extrapolates to the test point. |
127
+ | `KNORA-U` | Classification | Vote-count weighting. Each model earns one vote per neighbor it correctly classifies. |
128
+ | `KNORA-E` | Classification | Intersection-based. Only models correct on all neighbors survive; falls back to smaller neighborhoods. |
129
+ | `KNORA-IU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
130
+ | `OLA` | Both | Hard selection: only the single best model in the neighborhood contributes. |
128
131
 
129
132
  ---
130
133
 
@@ -171,13 +174,18 @@ def pinball(y_true, y_pred, alpha=0.9):
171
174
  e = y_true - y_pred
172
175
  return alpha * e if e >= 0 else (alpha - 1) * e
173
176
 
174
- router = KNNDWS(task="regression", metric=pinball, mode="min", k=20)
177
+ router = DEWSU(task="regression", metric=pinball, mode="min", k=20)
175
178
  ```
176
179
 
177
180
  Built-in metric strings: `accuracy`, `mae`, `mse`, `rmse`, `log_loss`, `prob_correct`.
178
181
 
179
182
  ---
180
183
 
184
+ ## Data types
185
+
186
+ deskit can be used with non-tabular data types like images, time series, and more. However, when used, the
187
+ passed features either need to be run through a feature extractor beforehand, such as a CNN backbone for images.
188
+
181
189
  ## Benchmark results
182
190
 
183
191
  100-seed benchmark (seeds 0–99) on standard sklearn and OpenML datasets. "Best Single" is the best
@@ -193,39 +201,39 @@ Pool: KNN, Decision Tree, SVR, Ridge, Bayesian Ridge.
193
201
 
194
202
  This pool was selected for having variability in architectures while avoiding a single dominant model.
195
203
 
196
- deskit algorithms tested: OLA, KNN-DWS, KNN-DWS-I, KNORA-U, KNORA-E, KNORA-IU.
204
+ deskit algorithms tested: OLA, DEWS-U, DEWS-I, DEWS-T, KNORA-U, KNORA-E, KNORA-IU.
197
205
 
198
206
  ### Regression (MAE, lower is better)
199
207
 
200
- % shown as delta vs Best Single. 100-seed mean.
208
+ % shown as delta vs Best Single. 20-seed mean.
201
209
 
202
- | Dataset | Best Single | Simple Avg | deskit best |
203
- |------------------------------|-------------|------------|-------------------------|
204
- | California Housing (sklearn) | 0.3955 | +7.93% | **−2.68%** (KNN-DWS-I) |
205
- | Bike Sharing (OpenML) | 51.604 | +48.39% | **−6.25%** (KNN-DWS-I) |
206
- | Abalone (OpenML) | **1.4923** | +1.29% | +1.61% (KNORA-IU) |
207
- | Diabetes (sklearn) | **44.986** | +2.98% | +0.88% (KNN-DWS-I) |
208
- | Concrete Strength (OpenML) | 5.3934 | +21.30% | **−2.85%** (KNORA-IU) |
210
+ | Dataset | Best Single | Simple Avg | deskit best |
211
+ |------------------------------|-------------|------------|---------------------------|
212
+ | California Housing (sklearn) | 0.3956 | +7.99% | **−2.54%** (DEWS-I) |
213
+ | Bike Sharing (OpenML) | 51.678 | +47.77% | **−6.86%** (DEWS-I) |
214
+ | Abalone (OpenML) | **1.4981** | +1.14% | +1.47% (KNORA-U/KNORA-IU) |
215
+ | Diabetes (sklearn) | **44.504** | +3.18% | +1.09% (DEWS-I/DEWS-T) |
216
+ | Concrete Strength (OpenML) | 5.2686 | +23.66% | **−1.20%** (DEWS-I) |
209
217
 
210
218
  deskit beats best single and simple averaging on 3/5 regression datasets. This shows how DES can provide a
211
219
  strong boost if used on the right dataset, but it might be counterproductive if used blindly.
212
220
 
213
221
  KNORA variants are designed for classification, which explains the poor performance
214
222
  on regression datasets; However, some exception can occur in certain datasets, either where
215
- feature space is has hard clusters (like in Concrete Strength) or when the target is discrete
223
+ feature space has hard clusters (like in Concrete Strength) or when the target is discrete
216
224
  and classification-like (like in Abalone).
217
225
 
218
226
  ### Classification (Accuracy, higher is better)
219
227
 
220
- % shown as delta vs Best Single. 100-seed mean.
228
+ % shown as delta vs Best Single. 20-seed mean.
221
229
 
222
- | Dataset | Best Single | Simple Avg | deskit best |
223
- |------------------------|-------------|------------|-------------------------|
224
- | HAR (OpenML) | 98.24% | −0.32% | **+0.14%** (KNN-DWS-I) |
225
- | Yeast (OpenML) | 59.19% | +0.46% | **+1.48%** (KNORA-IU) |
226
- | Image Segment (OpenML) | 93.65% | +1.70% | **+2.33%** (KNORA-IU) |
227
- | Waveform (OpenML) | **86.28%** | −1.04% | −0.55% (KNN-DWS-I) |
228
- | Vowel (OpenML) | 90.54% | −1.81% | **+0.93%** (KNORA-IU) |
230
+ | Dataset | Best Single | Simple Avg | deskit best |
231
+ |------------------------|-------------|------------|--------------------------|
232
+ | HAR (OpenML) | 98.24% | −0.33% | **+0.16%** (DEWS-T) |
233
+ | Yeast (OpenML) | 58.87% | +0.77% | **+1.66%** (KNORA-IU) |
234
+ | Image Segment (OpenML) | 93.70% | +1.40% | **+2.25%** (DEWS-T) |
235
+ | Waveform (OpenML) | **85.91%** | −0.98% | −0.39% (DEWS-T) |
236
+ | Vowel (OpenML) | 89.95% | −2.05% | **+0.93%** (KNORA-IU) |
229
237
 
230
238
  deskit beats or matches best single and simple averaging on 4/5 classification datasets. As seen on regression, DES
231
239
  can improve or hurt performance, so it must be used wisely, but if used correctly it can show promising results.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deskit"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "A Python library for Dynamic Ensemble Selection"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,13 +5,13 @@ Metrics
5
5
  -------
6
6
  Pass a metric name string:
7
7
 
8
- KNNDWS(task='classification', metric='log_loss', mode='min')
8
+ DEWSU(task='classification', metric='log_loss', mode='min')
9
9
 
10
10
  Or import a metric function directly:
11
11
 
12
12
  from deskit.metrics import log_loss, mae
13
13
 
14
- KNNDWS(task='classification', metric=log_loss, mode='min')
14
+ DEWSU(task='classification', metric=log_loss, mode='min')
15
15
 
16
16
  Available built-in metrics:
17
17
  Scalar predictions (pass predict() output):
@@ -21,7 +21,7 @@ Available built-in metrics:
21
21
  'log_loss', 'prob_correct'
22
22
  """
23
23
 
24
- from deskit.des.knndws import KNNDWS
24
+ from deskit.des.dewsu import DEWSU
25
25
  from deskit.des.ola import OLA
26
26
  from deskit.des.knorau import KNORAU
27
27
  from deskit.des.knorae import KNORAE
@@ -31,7 +31,7 @@ from deskit._config import SPEED_PRESETS, list_presets
31
31
  from deskit.analysis import analyze
32
32
 
33
33
  __all__ = [
34
- 'KNNDWS',
34
+ 'DEWSU',
35
35
  'OLA',
36
36
  'KNORAU',
37
37
  'KNORAE',
@@ -1,7 +1,7 @@
1
- from deskit.des.knndws import KNNDWS
1
+ from deskit.des.dewsu import DEWSU
2
2
  from deskit.des.ola import OLA
3
3
  from deskit.des.knorau import KNORAU
4
4
  from deskit.des.knorae import KNORAE
5
5
  from deskit.des.knoraiu import KNORAIU
6
6
 
7
- __all__ = ['KNNDWS', 'OLA', 'KNORAU', 'KNORAE', 'KNORAIU']
7
+ __all__ = ['DEWSU', 'OLA', 'KNORAU', 'KNORAE', 'KNORAIU']
@@ -1,5 +1,5 @@
1
1
  """
2
- KNN-DWS-IU: K-Nearest Neighbors with Distance-Weighted Softmax — Inverse-weighted Union.
2
+ DEWS-IU: K-Nearest Neighbors with Distance-Weighted Softmax — Inverse-weighted Union.
3
3
  """
4
4
  from deskit.base.knnbase import KNNBase
5
5
  from deskit._config import make_finder, resolve_metric, prep_fit_inputs
@@ -7,11 +7,11 @@ from deskit.utils import to_numpy
7
7
  import numpy as np
8
8
 
9
9
 
10
- class KNNDWSI(KNNBase):
10
+ class DEWSI(KNNBase):
11
11
  """
12
- KNN-DWS-IU: K-Nearest Neighbors with Distance-Weighted Softmax — Inverse-weighted Union.
12
+ DEWS-IU: K-Nearest Neighbors with Distance-Weighted Softmax — Inverse-weighted Union.
13
13
 
14
- Extends KNN-DWS by replacing the simple average of neighbor scores with an
14
+ Extends DEWS-U by replacing the simple average of neighbor scores with an
15
15
  inverse-distance-weighted average, so closer neighbors have a stronger
16
16
  influence on the softmax routing — analogous to how KNORA-IU extends KNORA-U.
17
17
 
@@ -0,0 +1,200 @@
1
+ """
2
+ DEWS-T: Distance-weighted Ensemble with Softmax — Trend.
3
+ """
4
+ from deskit.base.knnbase import KNNBase
5
+ from deskit._config import make_finder, resolve_metric, prep_fit_inputs
6
+ from deskit.utils import to_numpy
7
+ import numpy as np
8
+
9
+
10
+ _SIGNED_METRICS = {'mae', 'mse'}
11
+
12
+
13
+ def _signed_residual(y_true, y_pred):
14
+ return float(y_true) - float(y_pred)
15
+
16
+
17
+ class DEWST(KNNBase):
18
+ """
19
+ DEWS-T: Distance-weighted Ensemble with Softmax — Trend.
20
+ Parameters
21
+ ----------
22
+ task : str
23
+ 'classification' or 'regression'.
24
+ metric : str or callable
25
+ Scoring function. 'mae' or 'mse' activate signed-residual mode;
26
+ all other metrics are trended directly.
27
+ mode : str
28
+ 'max' if higher scores are better, 'min' if lower.
29
+ k : int
30
+ Neighbourhood size. Default: 10.
31
+ threshold : float
32
+ Competence gate. After per-neighbourhood normalisation (best=1.0,
33
+ worst=0.0), models below this fraction are excluded from softmax.
34
+ 0.0 disables the gate; 1.0 reduces to OLA behaviour. Default: 0.5.
35
+ temperature : float, optional
36
+ Softmax sharpness. Lower = sharper routing toward the local best model.
37
+ Defaults to 0.1 for min-metrics, 1.0 otherwise.
38
+ r2_threshold : float
39
+ Minimum weighted R² for the trend line to be trusted. Below this value
40
+ the sample falls back to DEWS-I scoring for that model. Default: 0.2.
41
+ preset : str
42
+ Neighbour search preset. Default: 'balanced'. See list_presets().
43
+ """
44
+
45
+ def __init__(self, task, metric='mae', mode='min', k=10,
46
+ threshold=0.5, temperature=None, r2_threshold=0.2,
47
+ preset='balanced', **kwargs):
48
+ metric_name, metric_fn = resolve_metric(metric)
49
+ finder = make_finder(preset, k, **kwargs)
50
+
51
+ self._use_signed = metric_name in _SIGNED_METRICS
52
+ self._metric_name = metric_name
53
+ self._convert = {'mae': np.abs, 'mse': np.square}.get(metric_name)
54
+
55
+ # For signed metrics, use signed residuals
56
+ super().__init__(
57
+ metric=_signed_residual if self._use_signed else metric_fn,
58
+ mode='max' if self._use_signed else mode,
59
+ neighbor_finder=finder
60
+ )
61
+
62
+ self._real_mode = mode
63
+ self.task = task
64
+ self.threshold = threshold
65
+ self._temperature = temperature
66
+ self.r2_threshold = r2_threshold
67
+
68
+ def fit(self, features, y, preds_dict):
69
+ """
70
+
71
+ Parameters
72
+ ----------
73
+ features : array-like, shape (n_val, n_features)
74
+ Validation features. Must not overlap with train or test data.
75
+ y : array-like, shape (n_val,)
76
+ Validation ground-truth labels or values.
77
+ preds_dict : dict[str, array-like]
78
+ Validation predictions keyed by model name.
79
+ """
80
+ features, y, preds_dict = prep_fit_inputs(
81
+ features, y, preds_dict, self._metric_name
82
+ )
83
+ super().fit(features, y, preds_dict)
84
+
85
+ def predict(self, x, temperature=None, threshold=None):
86
+ """
87
+
88
+ Parameters
89
+ ----------
90
+ x : array-like, shape (n_features,) or (n_samples, n_features)
91
+ temperature : float, optional
92
+ Overrides the instance temperature for this call.
93
+ threshold : float, optional
94
+ Overrides the instance threshold for this call.
95
+
96
+ Returns
97
+ -------
98
+ dict or list of dict
99
+ Single sample: {model_name: weight}. Batch: list of such dicts.
100
+ """
101
+ t = temperature if temperature is not None else (
102
+ self._temperature if self._temperature is not None else
103
+ (0.1 if self._real_mode == 'min' else 1.0))
104
+ th = threshold if threshold is not None else self.threshold
105
+
106
+ x = np.atleast_2d(to_numpy(x))
107
+ batch_size = x.shape[0]
108
+
109
+ distances, indices = self.model.kneighbors(x) # (batch, k)
110
+ k = distances.shape[1]
111
+
112
+ # Inverse-distance weights
113
+ inv_dist = 1.0 / np.maximum(distances, 1e-8) # (batch, k)
114
+ inv_dist_w = inv_dist / inv_dist.sum(axis=1, keepdims=True)
115
+
116
+ # Scores at each neighbour: (batch, k, n_models).
117
+ neighbor_scores = self.matrix[indices]
118
+
119
+ # Weighted least squares trend
120
+ d_max = distances.max(axis=1, keepdims=True)
121
+ d_norm = distances / np.where(d_max > 0, d_max, 1.0) # (batch, k)
122
+
123
+ # X^{T}WX: shape (batch, 2, 2)
124
+ W = inv_dist_w # (batch, k)
125
+ a = W.sum(axis=1) # (batch,)
126
+ b = (W * d_norm).sum(axis=1)
127
+ d_v = (W * d_norm ** 2).sum(axis=1)
128
+ det = a * d_v - b ** 2 # (batch,)
129
+ bad_det = np.abs(det) <= 1e-12
130
+ det_safe = np.where(bad_det, 1.0, det)
131
+
132
+ # XᵀWy for all models: shape (batch, 2, n_models).
133
+ Wy = neighbor_scores * inv_dist_w[:, :, np.newaxis] # (batch, k, n_models)
134
+ Wdy = Wy * d_norm[:, :, np.newaxis]
135
+ XtWy_0 = Wy.sum(axis=1) # (batch, n_models)
136
+ XtWy_1 = Wdy.sum(axis=1) # (batch, n_models)
137
+
138
+ # Closed-form 2×2 inverse applied.
139
+ # intercept B0
140
+ # slope B1
141
+ intercept = (d_v[:, np.newaxis] * XtWy_0 -
142
+ b[:, np.newaxis] * XtWy_1) / det_safe[:, np.newaxis]
143
+ slope = (a[:, np.newaxis] * XtWy_1 -
144
+ b[:, np.newaxis] * XtWy_0) / det_safe[:, np.newaxis]
145
+
146
+ # Weighted R^2
147
+ y_hat = (intercept[:, np.newaxis, :] +
148
+ slope[:, np.newaxis, :] *
149
+ d_norm[:, :, np.newaxis]) # (batch, k, n_models)
150
+ y_wmean = XtWy_0 # weighted mean
151
+ ss_res = (inv_dist_w[:, :, np.newaxis] *
152
+ (neighbor_scores - y_hat) ** 2).sum(axis=1)
153
+ ss_tot = (inv_dist_w[:, :, np.newaxis] *
154
+ (neighbor_scores - y_wmean[:, np.newaxis, :]) ** 2).sum(axis=1)
155
+ r2 = np.where(ss_tot > 1e-12, 1.0 - ss_res / ss_tot, 0.0)
156
+ # Bad determinant = fallback.
157
+ r2 = np.where(bad_det[:, np.newaxis], 0.0, r2) # (batch, n_models)
158
+
159
+ # DEWS-I fallback
160
+ if self._use_signed:
161
+ # Convert signed residuals back to metric
162
+ fallback_raw = self._convert(neighbor_scores)
163
+ dewsi_scores = -(fallback_raw * inv_dist_w[:, :, np.newaxis]).sum(axis=1)
164
+ else:
165
+ dewsi_scores = XtWy_0
166
+
167
+ # Convert trend intercept to routing scord
168
+ if self._use_signed:
169
+ trend_scores = -self._convert(intercept) # negate for min-routing
170
+ else:
171
+ trend_scores = intercept
172
+
173
+ # Blend: trust trend where R² ≥ threshold, fall back otherwise.
174
+ use_trend = r2 >= self.r2_threshold
175
+ avg_scores = np.where(use_trend, trend_scores, dewsi_scores)
176
+
177
+ # Standard DEWS softmax
178
+ local_min = avg_scores.min(axis=1, keepdims=True)
179
+ local_max = avg_scores.max(axis=1, keepdims=True)
180
+ local_range = local_max - local_min
181
+ norm_scores = (avg_scores - local_min) / np.where(local_range > 0, local_range, 1.0)
182
+
183
+ if th > 0:
184
+ gate = norm_scores >= th
185
+ any_pass = gate.any(axis=1, keepdims=True)
186
+ gate = np.where(any_pass, gate, norm_scores == 1.0)
187
+ norm_scores = norm_scores * gate
188
+
189
+ max_scores = norm_scores.max(axis=1, keepdims=True)
190
+ exp_scores = np.exp((norm_scores - max_scores) / t)
191
+ if th > 0:
192
+ exp_scores = exp_scores * gate
193
+ total = exp_scores.sum(axis=1, keepdims=True)
194
+ weights = np.where(total > 0,
195
+ exp_scores / np.where(total > 0, total, 1.0),
196
+ np.full_like(exp_scores, 1.0 / len(self.models)))
197
+
198
+ if batch_size == 1:
199
+ return dict(zip(self.models, weights[0]))
200
+ return [dict(zip(self.models, w)) for w in weights]
@@ -1,5 +1,5 @@
1
1
  """
2
- KNN-DWS: K-Nearest Neighbors with Distance-Weighted Softmax.
2
+ DEWS-U: K-Nearest Neighbors with Distance-Weighted Softmax.
3
3
  """
4
4
  from deskit.base.knnbase import KNNBase
5
5
  from deskit._config import make_finder, resolve_metric, prep_fit_inputs
@@ -7,9 +7,9 @@ from deskit.utils import to_numpy
7
7
  import numpy as np
8
8
 
9
9
 
10
- class KNNDWS(KNNBase):
10
+ class DEWSU(KNNBase):
11
11
  """
12
- KNN-DWS: K-Nearest Neighbors with Distance-Weighted Softmax.
12
+ DEWS-U: K-Nearest Neighbors with Distance-Weighted Softmax.
13
13
 
14
14
  Parameters
15
15
  ----------
@@ -3,7 +3,7 @@ DynamicRouter — string-based factory for programmatic algorithm selection.
3
3
 
4
4
  Use DynamicRouter when you need to choose an algorithm via a string at runtime.
5
5
  """
6
- from deskit.des.knndws import KNNDWS
6
+ from deskit.des.dewsu import DEWSU
7
7
  from deskit.des.ola import OLA
8
8
  from deskit.des.knorau import KNORAU
9
9
  from deskit.des.knorae import KNORAE
@@ -12,7 +12,7 @@ from deskit._config import SPEED_PRESETS, list_presets
12
12
  from deskit.utils import to_numpy, add_batch_dim
13
13
 
14
14
  _METHOD_CLASSES = {
15
- 'knn-dws': KNNDWS,
15
+ 'DEWS-U': DEWSU,
16
16
  'ola': OLA,
17
17
  'knora-u': KNORAU,
18
18
  'knora-e': KNORAE,
@@ -29,7 +29,7 @@ class DynamicRouter:
29
29
  task : str
30
30
  'classification' or 'regression'.
31
31
  method : str
32
- 'knn-dws', 'ola', 'knora-u', or 'knora-e'.
32
+ 'DEWS-U', 'ola', 'knora-u', or 'knora-e'.
33
33
  metric : str or callable
34
34
  Per-sample scoring function. Built-in names: 'accuracy', 'mae', 'mse',
35
35
  'rmse', 'log_loss', 'prob_correct'. Or any callable (y_true, y_pred) -> float.
@@ -40,7 +40,7 @@ class DynamicRouter:
40
40
  threshold : float
41
41
  Competence gate applied after per-neighborhood normalization.
42
42
  temperature : float, optional
43
- Softmax sharpness for knn-dws. Ignored by other algorithms.
43
+ Softmax sharpness for DEWS-U. Ignored by other algorithms.
44
44
  preset : str
45
45
  Speed/accuracy preset. Call list_presets() for options.
46
46
  feature_extractor : callable, optional
@@ -51,7 +51,7 @@ class DynamicRouter:
51
51
  Forwarded to the neighbor finder constructor.
52
52
  """
53
53
 
54
- def __init__(self, task, method='knn-dws', metric='accuracy', mode='max',
54
+ def __init__(self, task, method='DEWS-U', metric='accuracy', mode='max',
55
55
  k=10, threshold=0.5, temperature=None, preset='balanced',
56
56
  feature_extractor=None, finder=None, **kwargs):
57
57
 
@@ -71,8 +71,8 @@ class DynamicRouter:
71
71
  # Pass finder through as a kwarg when using preset='custom'.
72
72
  extra = {'finder': finder} if finder is not None else {}
73
73
 
74
- # KNNDWS accepts temperature; the others don't.
75
- if method == 'knn-dws':
74
+ # DEWSU accepts temperature; the others don't.
75
+ if method == 'DEWS-U':
76
76
  self._des = cls(
77
77
  task=task, metric=metric, mode=mode, k=k,
78
78
  threshold=threshold, temperature=temperature,
@@ -108,7 +108,7 @@ class DynamicRouter:
108
108
  ----------
109
109
  x : array-like, shape (n_features,) or (n_samples, n_features)
110
110
  temperature : float, optional
111
- knn-dws only. Overrides the instance temperature for this call.
111
+ DEWS-U only. Overrides the instance temperature for this call.
112
112
  threshold : float, optional
113
113
  Overrides the instance threshold for this call.
114
114
 
@@ -125,7 +125,7 @@ class DynamicRouter:
125
125
  # Class methods
126
126
 
127
127
  @classmethod
128
- def from_data_size(cls, n_samples, n_features, task, method='knn-dws',
128
+ def from_data_size(cls, n_samples, n_features, task, method='DEWS-U',
129
129
  metric='accuracy', mode='max', k=10, threshold=0.5,
130
130
  n_queries=None, **extra_kwargs):
131
131
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deskit
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: A Python library for Dynamic Ensemble Selection
5
5
  Author: Tikhon Vodyanov
6
6
  License-Expression: MIT
@@ -31,7 +31,7 @@ Dynamic: license-file
31
31
 
32
32
  # deskit
33
33
 
34
- [deskit](https://TikaaVo.github.io/deskit/) is a flexible, lightweight, and easy-to-use ensembling library that implements
34
+ deskit is a flexible, lightweight, and easy-to-use ensembling library that implements
35
35
  Dynamic Ensemble Selection (DES) algorithms for ensembling multiple ML models
36
36
  on a given dataset.
37
37
 
@@ -43,6 +43,8 @@ requiring any wrappers, including custom models, popular ML libraries, and APIs.
43
43
  deskit includes several DES algorithms, and it works with both classification
44
44
  and regression.
45
45
 
46
+ See the full documentation [here](https://TikaaVo.github.io/deskit/).
47
+
46
48
  # Dynamic Ensemble Selection
47
49
 
48
50
  Ensemble learning in machine learning refers to when multiple models trained on a
@@ -148,14 +150,15 @@ weights = router.predict(X_test[i])
148
150
 
149
151
  ## Algorithms
150
152
 
151
- | Method | Best for | Notes |
152
- |-----------|---|----------------------------------------------------------------------------------------------------------|
153
- | `KNNDWS` | Regression | Softmax over neighbourhood-averaged scores. Temperature controls sharpness. |
154
- | `KNNDWSI` | Regression | Like KNN-DWS but scores are inverse-distance weighted. |
155
- | `KNORAU` | Classification | Vote-count weighting. Each model earns one vote per neighbour it correctly classifies. |
156
- | `KNORAE` | Classification | Intersection-based. Only models correct on all neighbours survive; falls back to smaller neighbourhoods. |
157
- | `KNORAIU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
158
- | `OLA` | Both | Hard selection: only the single best model in the neighbourhood contributes. |
153
+ | Method | Best for | Notes |
154
+ |------------|----------------|------------------------------------------------------------------------------------------------------|
155
+ | `DEWS-U` | Regression | Softmax over neighborhood-averaged scores. Temperature controls sharpness. |
156
+ | `DEWS-I` | Regression | Like DEWS-U but scores are inverse-distance weighted. |
157
+ | `DEWS-T` | Both | Like DEWS-U but fits a weighted trend line over neighbor scores and extrapolates to the test point. |
158
+ | `KNORA-U` | Classification | Vote-count weighting. Each model earns one vote per neighbor it correctly classifies. |
159
+ | `KNORA-E` | Classification | Intersection-based. Only models correct on all neighbors survive; falls back to smaller neighborhoods. |
160
+ | `KNORA-IU` | Classification | Like KNORA-U but votes are inverse-distance weighted. |
161
+ | `OLA` | Both | Hard selection: only the single best model in the neighborhood contributes. |
159
162
 
160
163
  ---
161
164
 
@@ -202,13 +205,18 @@ def pinball(y_true, y_pred, alpha=0.9):
202
205
  e = y_true - y_pred
203
206
  return alpha * e if e >= 0 else (alpha - 1) * e
204
207
 
205
- router = KNNDWS(task="regression", metric=pinball, mode="min", k=20)
208
+ router = DEWSU(task="regression", metric=pinball, mode="min", k=20)
206
209
  ```
207
210
 
208
211
  Built-in metric strings: `accuracy`, `mae`, `mse`, `rmse`, `log_loss`, `prob_correct`.
209
212
 
210
213
  ---
211
214
 
215
+ ## Data types
216
+
217
+ deskit can be used with non-tabular data types like images, time series, and more. However, when used, the
218
+ passed features either need to be run through a feature extractor beforehand, such as a CNN backbone for images.
219
+
212
220
  ## Benchmark results
213
221
 
214
222
  100-seed benchmark (seeds 0–99) on standard sklearn and OpenML datasets. "Best Single" is the best
@@ -224,39 +232,39 @@ Pool: KNN, Decision Tree, SVR, Ridge, Bayesian Ridge.
224
232
 
225
233
  This pool was selected for having variability in architectures while avoiding a single dominant model.
226
234
 
227
- deskit algorithms tested: OLA, KNN-DWS, KNN-DWS-I, KNORA-U, KNORA-E, KNORA-IU.
235
+ deskit algorithms tested: OLA, DEWS-U, DEWS-I, DEWS-T, KNORA-U, KNORA-E, KNORA-IU.
228
236
 
229
237
  ### Regression (MAE, lower is better)
230
238
 
231
- % shown as delta vs Best Single. 100-seed mean.
239
+ % shown as delta vs Best Single. 20-seed mean.
232
240
 
233
- | Dataset | Best Single | Simple Avg | deskit best |
234
- |------------------------------|-------------|------------|-------------------------|
235
- | California Housing (sklearn) | 0.3955 | +7.93% | **−2.68%** (KNN-DWS-I) |
236
- | Bike Sharing (OpenML) | 51.604 | +48.39% | **−6.25%** (KNN-DWS-I) |
237
- | Abalone (OpenML) | **1.4923** | +1.29% | +1.61% (KNORA-IU) |
238
- | Diabetes (sklearn) | **44.986** | +2.98% | +0.88% (KNN-DWS-I) |
239
- | Concrete Strength (OpenML) | 5.3934 | +21.30% | **−2.85%** (KNORA-IU) |
241
+ | Dataset | Best Single | Simple Avg | deskit best |
242
+ |------------------------------|-------------|------------|---------------------------|
243
+ | California Housing (sklearn) | 0.3956 | +7.99% | **−2.54%** (DEWS-I) |
244
+ | Bike Sharing (OpenML) | 51.678 | +47.77% | **−6.86%** (DEWS-I) |
245
+ | Abalone (OpenML) | **1.4981** | +1.14% | +1.47% (KNORA-U/KNORA-IU) |
246
+ | Diabetes (sklearn) | **44.504** | +3.18% | +1.09% (DEWS-I/DEWS-T) |
247
+ | Concrete Strength (OpenML) | 5.2686 | +23.66% | **−1.20%** (DEWS-I) |
240
248
 
241
249
  deskit beats best single and simple averaging on 3/5 regression datasets. This shows how DES can provide a
242
250
  strong boost if used on the right dataset, but it might be counterproductive if used blindly.
243
251
 
244
252
  KNORA variants are designed for classification, which explains the poor performance
245
253
  on regression datasets; However, some exception can occur in certain datasets, either where
246
- feature space is has hard clusters (like in Concrete Strength) or when the target is discrete
254
+ feature space has hard clusters (like in Concrete Strength) or when the target is discrete
247
255
  and classification-like (like in Abalone).
248
256
 
249
257
  ### Classification (Accuracy, higher is better)
250
258
 
251
- % shown as delta vs Best Single. 100-seed mean.
259
+ % shown as delta vs Best Single. 20-seed mean.
252
260
 
253
- | Dataset | Best Single | Simple Avg | deskit best |
254
- |------------------------|-------------|------------|-------------------------|
255
- | HAR (OpenML) | 98.24% | −0.32% | **+0.14%** (KNN-DWS-I) |
256
- | Yeast (OpenML) | 59.19% | +0.46% | **+1.48%** (KNORA-IU) |
257
- | Image Segment (OpenML) | 93.65% | +1.70% | **+2.33%** (KNORA-IU) |
258
- | Waveform (OpenML) | **86.28%** | −1.04% | −0.55% (KNN-DWS-I) |
259
- | Vowel (OpenML) | 90.54% | −1.81% | **+0.93%** (KNORA-IU) |
261
+ | Dataset | Best Single | Simple Avg | deskit best |
262
+ |------------------------|-------------|------------|--------------------------|
263
+ | HAR (OpenML) | 98.24% | −0.33% | **+0.16%** (DEWS-T) |
264
+ | Yeast (OpenML) | 58.87% | +0.77% | **+1.66%** (KNORA-IU) |
265
+ | Image Segment (OpenML) | 93.70% | +1.40% | **+2.25%** (DEWS-T) |
266
+ | Waveform (OpenML) | **85.91%** | −0.98% | −0.39% (DEWS-T) |
267
+ | Vowel (OpenML) | 89.95% | −2.05% | **+0.93%** (KNORA-IU) |
260
268
 
261
269
  deskit beats or matches best single and simple averaging on 4/5 classification datasets. As seen on regression, DES
262
270
  can improve or hurt performance, so it must be used wisely, but if used correctly it can show promising results.
@@ -17,8 +17,9 @@ src/deskit/base/__init__.py
17
17
  src/deskit/base/base.py
18
18
  src/deskit/base/knnbase.py
19
19
  src/deskit/des/__init__.py
20
- src/deskit/des/knndws.py
21
- src/deskit/des/knndwsi.py
20
+ src/deskit/des/dewsi.py
21
+ src/deskit/des/dewst.py
22
+ src/deskit/des/dewsu.py
22
23
  src/deskit/des/knorae.py
23
24
  src/deskit/des/knoraiu.py
24
25
  src/deskit/des/knorau.py
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