fast-select 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast_select/MultiSURF.py +491 -0
- fast_select/ReliefF.py +457 -0
- fast_select/SURF.py +432 -0
- fast_select/TuRF.py +143 -0
- fast_select/__init__.py +6 -0
- fast_select-0.1.3.dist-info/METADATA +225 -0
- fast_select-0.1.3.dist-info/RECORD +10 -0
- fast_select-0.1.3.dist-info/WHEEL +5 -0
- fast_select-0.1.3.dist-info/licenses/LICENSE +21 -0
- fast_select-0.1.3.dist-info/top_level.txt +1 -0
fast_select/MultiSURF.py
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import math
|
|
3
|
+
import warnings
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numba import cuda, float32, int32, njit, prange, config, get_num_threads, set_num_threads
|
|
6
|
+
from numba.core.errors import NumbaPerformanceWarning
|
|
7
|
+
from sklearn.base import BaseEstimator, TransformerMixin
|
|
8
|
+
from sklearn.utils.validation import check_array, check_is_fitted,check_X_y, validate_data
|
|
9
|
+
warnings.simplefilter("ignore", category=NumbaPerformanceWarning)
|
|
10
|
+
|
|
11
|
+
TPB = 64 # Threads Per Block
|
|
12
|
+
MAx_F_TILE = 1024 # Features loaded per shared-memory tile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cuda.jit
|
|
16
|
+
def _multisurf_gpu_kernel(x, y, recip_full, feat_idx, n_kept, use_star, is_discrete, scores_out):
|
|
17
|
+
"""MultiSURF scoring for an _arbitrary subset_ of features."""
|
|
18
|
+
n_samples = x.shape[0]
|
|
19
|
+
i = cuda.blockIdx.x # focal sample index
|
|
20
|
+
tid = cuda.threadIdx.x
|
|
21
|
+
# Shared scratch
|
|
22
|
+
hits_tile = cuda.shared.array(shape=MAx_F_TILE, dtype=float32)
|
|
23
|
+
miss_tile = cuda.shared.array(shape=MAx_F_TILE, dtype=float32)
|
|
24
|
+
sh_red_f32 = cuda.shared.array(shape=TPB, dtype=float32)
|
|
25
|
+
sh_red_i32 = cuda.shared.array(shape=TPB, dtype=int32)
|
|
26
|
+
|
|
27
|
+
sum_d = 0.0
|
|
28
|
+
sum_d2 = 0.0
|
|
29
|
+
for j in range(tid, n_samples, TPB):
|
|
30
|
+
if j == i:
|
|
31
|
+
continue
|
|
32
|
+
dist = 0.0
|
|
33
|
+
for f0 in range(0, n_kept, MAx_F_TILE):
|
|
34
|
+
tile_len = min(MAx_F_TILE, n_kept - f0)
|
|
35
|
+
for f in range(tile_len):
|
|
36
|
+
full_idx = feat_idx[f0 + f]
|
|
37
|
+
if is_discrete[full_idx]:
|
|
38
|
+
diff = 1.0 if x[i, full_idx] != x[j, full_idx] else 0.0
|
|
39
|
+
else:
|
|
40
|
+
diff = abs(x[i, full_idx] - x[j, full_idx]) * recip_full[full_idx]
|
|
41
|
+
dist += diff
|
|
42
|
+
sum_d += dist
|
|
43
|
+
sum_d2 += dist * dist
|
|
44
|
+
# Reduce for mean
|
|
45
|
+
sh_red_f32[tid] = sum_d
|
|
46
|
+
cuda.syncthreads()
|
|
47
|
+
off = TPB // 2
|
|
48
|
+
while off:
|
|
49
|
+
if tid < off:
|
|
50
|
+
sh_red_f32[tid] += sh_red_f32[tid + off]
|
|
51
|
+
off //= 2
|
|
52
|
+
cuda.syncthreads()
|
|
53
|
+
mu = sh_red_f32[0] / (n_samples - 1)
|
|
54
|
+
# Reduce for variance
|
|
55
|
+
sh_red_f32[tid] = sum_d2
|
|
56
|
+
cuda.syncthreads()
|
|
57
|
+
off = TPB // 2
|
|
58
|
+
while off:
|
|
59
|
+
if tid < off:
|
|
60
|
+
sh_red_f32[tid] += sh_red_f32[tid + off]
|
|
61
|
+
off //= 2
|
|
62
|
+
cuda.syncthreads()
|
|
63
|
+
var = sh_red_f32[0] / (n_samples - 1) - mu * mu
|
|
64
|
+
sigma = math.sqrt(max(var, 0.0))
|
|
65
|
+
thresh = mu - 0.5 * sigma
|
|
66
|
+
|
|
67
|
+
for f0 in range(0, n_kept, MAx_F_TILE):
|
|
68
|
+
tile_len = min(MAx_F_TILE, n_kept - f0)
|
|
69
|
+
if tid < tile_len:
|
|
70
|
+
hits_tile[tid] = 0.0
|
|
71
|
+
miss_tile[tid] = 0.0
|
|
72
|
+
cuda.syncthreads()
|
|
73
|
+
n_hit_local = 0
|
|
74
|
+
n_miss_local = 0
|
|
75
|
+
for j in range(tid, n_samples, TPB):
|
|
76
|
+
if j == i:
|
|
77
|
+
continue
|
|
78
|
+
dist = 0.0
|
|
79
|
+
for f in range(tile_len):
|
|
80
|
+
full_idx = feat_idx[f0 + f]
|
|
81
|
+
if is_discrete[full_idx]: # Use binary differences for discrete features
|
|
82
|
+
diff = 1.0 if x[i, full_idx] != x[j, full_idx] else 0.0
|
|
83
|
+
else: # Use scaled continuous difference for continuous features
|
|
84
|
+
diff = abs(x[i, full_idx] - x[j, full_idx]) * recip_full[full_idx]
|
|
85
|
+
dist += diff
|
|
86
|
+
is_hit = y[i] == y[j]
|
|
87
|
+
if dist < thresh: # This is a NEAR neighbor
|
|
88
|
+
for f in range(tile_len):
|
|
89
|
+
full_idx = feat_idx[f0 + f]
|
|
90
|
+
diff = abs(x[i, full_idx] - x[j, full_idx]) * recip_full[full_idx]
|
|
91
|
+
if is_hit:
|
|
92
|
+
cuda.atomic.add(hits_tile, f, diff)
|
|
93
|
+
else:
|
|
94
|
+
cuda.atomic.add(miss_tile, f, diff)
|
|
95
|
+
if is_hit:
|
|
96
|
+
n_hit_local += 1
|
|
97
|
+
else:
|
|
98
|
+
n_miss_local += 1
|
|
99
|
+
elif use_star and not is_hit: # This is a FAR MISS
|
|
100
|
+
for f in range(tile_len):
|
|
101
|
+
full_idx = feat_idx[f0 + f]
|
|
102
|
+
diff = abs(x[i, full_idx] - x[j, full_idx]) * recip_full[full_idx]
|
|
103
|
+
cuda.atomic.add(miss_tile, f, -diff)
|
|
104
|
+
cuda.syncthreads()
|
|
105
|
+
# Shared reduction of neighbour counts
|
|
106
|
+
sh_red_i32[tid] = n_hit_local
|
|
107
|
+
cuda.syncthreads()
|
|
108
|
+
off = TPB // 2
|
|
109
|
+
while off:
|
|
110
|
+
if tid < off:
|
|
111
|
+
sh_red_i32[tid] += sh_red_i32[tid + off]
|
|
112
|
+
off //= 2
|
|
113
|
+
cuda.syncthreads()
|
|
114
|
+
total_hits = sh_red_i32[0]
|
|
115
|
+
sh_red_i32[tid] = n_miss_local
|
|
116
|
+
cuda.syncthreads()
|
|
117
|
+
off = TPB // 2
|
|
118
|
+
while off:
|
|
119
|
+
if tid < off:
|
|
120
|
+
sh_red_i32[tid] += sh_red_i32[tid + off]
|
|
121
|
+
off //= 2
|
|
122
|
+
cuda.syncthreads()
|
|
123
|
+
total_miss = sh_red_i32[0]
|
|
124
|
+
if tid < tile_len:
|
|
125
|
+
local_idx = f0 + tid
|
|
126
|
+
term = 0.0
|
|
127
|
+
if total_miss > 0:
|
|
128
|
+
term += miss_tile[tid] / total_miss
|
|
129
|
+
if total_hits > 0:
|
|
130
|
+
term -= hits_tile[tid] / total_hits
|
|
131
|
+
cuda.atomic.add(scores_out, local_idx, term)
|
|
132
|
+
cuda.syncthreads()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _compute_ranges(x: np.ndarray) -> np.ndarray:
|
|
136
|
+
"""Helper to compute feature ranges on the CPU."""
|
|
137
|
+
ranges = (x.max(axis=0) - x.min(axis=0)).astype(np.float32)
|
|
138
|
+
return ranges
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _multisurf_gpu_host_caller(
|
|
142
|
+
x_d, y_d, recip_full_d, feat_idx: np.ndarray, use_star: bool, is_discrete: np.ndarray
|
|
143
|
+
) -> np.ndarray:
|
|
144
|
+
"""Host helper function that launches the kernel and returns scores."""
|
|
145
|
+
n_samples, _ = x_d.shape
|
|
146
|
+
n_kept = feat_idx.size
|
|
147
|
+
feat_idx_d = cuda.to_device(feat_idx.astype(np.int64, copy=False))
|
|
148
|
+
scores_d = cuda.device_array(n_kept, dtype=np.float32)
|
|
149
|
+
scores_d[:] = 0.0 # Zero-fill on device
|
|
150
|
+
|
|
151
|
+
_multisurf_gpu_kernel[n_samples, TPB](
|
|
152
|
+
x_d, y_d, recip_full_d, feat_idx_d, n_kept, use_star, is_discrete, scores_d
|
|
153
|
+
)
|
|
154
|
+
cuda.synchronize()
|
|
155
|
+
|
|
156
|
+
return scores_d.copy_to_host() / n_samples
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@njit(parallel=True, fastmath=True)
|
|
160
|
+
def _multisurf_cpu_kernel(x, y, recip_full, feat_idx, n_kept, use_star, is_discrete, scores_out):
|
|
161
|
+
"""
|
|
162
|
+
Optimized MultiSURF scoring for CPU.
|
|
163
|
+
"""
|
|
164
|
+
n_samples = x.shape[0]
|
|
165
|
+
|
|
166
|
+
temp_scores = np.zeros((n_samples, n_kept), dtype=np.float32)
|
|
167
|
+
|
|
168
|
+
for i in prange(n_samples):
|
|
169
|
+
sum_d = 0.0
|
|
170
|
+
sum_d2 = 0.0
|
|
171
|
+
for j in range(n_samples):
|
|
172
|
+
if i == j:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
dist = 0.0
|
|
176
|
+
for k in range(n_kept):
|
|
177
|
+
f = feat_idx[k]
|
|
178
|
+
if is_discrete[f]:
|
|
179
|
+
diff = 1.0 if x[i, f] != x[j, f] else 0.0
|
|
180
|
+
else:
|
|
181
|
+
diff = abs(x[i, f] - x[j, f]) * recip_full[f]
|
|
182
|
+
dist += diff
|
|
183
|
+
|
|
184
|
+
sum_d += dist
|
|
185
|
+
sum_d2 += dist * dist
|
|
186
|
+
|
|
187
|
+
mu = sum_d / (n_samples - 1)
|
|
188
|
+
var = max(0.0, (sum_d2 / (n_samples - 1)) - (mu * mu))
|
|
189
|
+
sigma = math.sqrt(var)
|
|
190
|
+
thresh = mu - 0.5 * sigma
|
|
191
|
+
|
|
192
|
+
hit_diffs = np.zeros(n_kept, dtype=np.float32)
|
|
193
|
+
miss_diffs = np.zeros(n_kept, dtype=np.float32)
|
|
194
|
+
n_hits = 0
|
|
195
|
+
n_miss = 0
|
|
196
|
+
|
|
197
|
+
for j in range(n_samples):
|
|
198
|
+
if i == j:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
dist = 0.0
|
|
202
|
+
for k in range(n_kept):
|
|
203
|
+
f = feat_idx[k]
|
|
204
|
+
if is_discrete[f]:
|
|
205
|
+
diff = 1.0 if x[i, f] != x[j, f] else 0.0
|
|
206
|
+
else:
|
|
207
|
+
diff = abs(x[i, f] - x[j, f]) * recip_full[f] # Continuous difference
|
|
208
|
+
dist += diff
|
|
209
|
+
|
|
210
|
+
is_hit = y[i] == y[j]
|
|
211
|
+
if dist < thresh: # NEAR neighbor
|
|
212
|
+
if is_hit:
|
|
213
|
+
n_hits += 1
|
|
214
|
+
for k in range(n_kept):
|
|
215
|
+
f = feat_idx[k]
|
|
216
|
+
if is_discrete[f]:
|
|
217
|
+
diff = 1.0 if x[i, f] != x[j, f] else 0.0
|
|
218
|
+
else:
|
|
219
|
+
diff = abs(x[i, f] - x[j, f]) * recip_full[f]
|
|
220
|
+
hit_diffs[k] += diff
|
|
221
|
+
else:
|
|
222
|
+
n_miss += 1
|
|
223
|
+
for k in range(n_kept):
|
|
224
|
+
f = feat_idx[k]
|
|
225
|
+
if is_discrete[f]:
|
|
226
|
+
diff = 1.0 if x[i, f] != x[j, f] else 0.0
|
|
227
|
+
else:
|
|
228
|
+
diff = abs(x[i, f] - x[j, f]) * recip_full[f]
|
|
229
|
+
miss_diffs[k] += diff
|
|
230
|
+
elif use_star and not is_hit: # FAR MISS
|
|
231
|
+
for k in range(n_kept):
|
|
232
|
+
f = feat_idx[k]
|
|
233
|
+
if is_discrete[f]:
|
|
234
|
+
diff = 1.0 if x[i, f] != x[j, f] else 0.0
|
|
235
|
+
else:
|
|
236
|
+
diff = abs(x[i, f] - x[j, f]) * recip_full[f]
|
|
237
|
+
miss_diffs[k] -= diff
|
|
238
|
+
|
|
239
|
+
if n_hits > 0:
|
|
240
|
+
hit_diffs /= n_hits
|
|
241
|
+
if n_miss > 0:
|
|
242
|
+
miss_diffs /= n_miss
|
|
243
|
+
|
|
244
|
+
for k in range(n_kept):
|
|
245
|
+
temp_scores[i, k] = miss_diffs[k] - hit_diffs[k]
|
|
246
|
+
for k in range(n_kept):
|
|
247
|
+
scores_out[k] = temp_scores[:, k].sum()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _multisurf_cpu_host_caller(x, y, recip_full, feat_idx, use_star, is_discrete, n_jobs):
|
|
251
|
+
"""Host caller for the optimized CPU kernel."""
|
|
252
|
+
n_samples = x.shape[0]
|
|
253
|
+
n_kept = feat_idx.size
|
|
254
|
+
scores = np.zeros(n_kept, dtype=np.float32)
|
|
255
|
+
num_threads_to_set = config.NUMBA_NUM_THREADS if n_jobs == -1 else n_jobs
|
|
256
|
+
original_num_threads = get_num_threads()
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
set_num_threads(num_threads_to_set)
|
|
260
|
+
_multisurf_cpu_kernel(x, y, recip_full, feat_idx, n_kept, use_star, is_discrete, scores)
|
|
261
|
+
finally:
|
|
262
|
+
set_num_threads(original_num_threads)
|
|
263
|
+
|
|
264
|
+
return scores / n_samples
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class MultiSURF(TransformerMixin, BaseEstimator):
|
|
268
|
+
"""GPU and CPU-accelerated feature selection using the MultiSURF algorithm.
|
|
269
|
+
|
|
270
|
+
This estimator provides a unified API for running MultiSURF on either
|
|
271
|
+
a CPU (using Numba's parallel JIT) or a GPU (using Numba CUDA).
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
n_features_to_select : int, default=10
|
|
276
|
+
The number of top features to select.
|
|
277
|
+
|
|
278
|
+
backend : {'auto', 'gpu', 'cpu'}, default='auto'
|
|
279
|
+
The compute backend to use.
|
|
280
|
+
- 'auto': Use 'gpu' if a compatible NVIDIA GPU is detected,
|
|
281
|
+
otherwise fall back to 'cpu'.
|
|
282
|
+
- 'gpu': Force use of the GPU. Raises an error if not available.
|
|
283
|
+
- 'cpu': Force use of the CPU.
|
|
284
|
+
|
|
285
|
+
use_star : bool, default=False
|
|
286
|
+
Whether to run the MultiSURF* adaptation of the algorithm.
|
|
287
|
+
By default, the standard MultiSURF algorithm is used.
|
|
288
|
+
|
|
289
|
+
discrete_limit : int, default=10
|
|
290
|
+
The limit of individual feature values to determine whether or not
|
|
291
|
+
a given feature is discrete or continuous. (Effects distance
|
|
292
|
+
calculation)
|
|
293
|
+
|
|
294
|
+
verbose : bool, default=False
|
|
295
|
+
Controls whether progress updates are printed during the fit.
|
|
296
|
+
Limited benefit currently, will be expanded in future versions.
|
|
297
|
+
|
|
298
|
+
n_jobs : int, default=-1
|
|
299
|
+
Controls the number of threads utilized by Numba while running on the cpu.
|
|
300
|
+
-1 uses all threads avaliable by default. Set to a low number if experiencing
|
|
301
|
+
difficulties and lagging running the script.
|
|
302
|
+
|
|
303
|
+
Attributes
|
|
304
|
+
----------
|
|
305
|
+
n_features_in_ : int
|
|
306
|
+
The number of features seen during `fit`.
|
|
307
|
+
|
|
308
|
+
feature_importances_ : ndarray of shape (n_features,)
|
|
309
|
+
The calculated importance scores for each feature.
|
|
310
|
+
|
|
311
|
+
effective_backend_ : str
|
|
312
|
+
The backend that was actually used during `fit` ('gpu' or 'cpu').
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def __init__(
|
|
316
|
+
self,
|
|
317
|
+
n_features_to_select: int | float = 0.2,
|
|
318
|
+
backend: str = "auto",
|
|
319
|
+
use_star: bool = False,
|
|
320
|
+
discrete_limit: int = 10,
|
|
321
|
+
n_jobs: int = -1,
|
|
322
|
+
verbose: bool = False,
|
|
323
|
+
):
|
|
324
|
+
self.n_features_to_select = n_features_to_select
|
|
325
|
+
self.backend = backend
|
|
326
|
+
self.use_star = use_star
|
|
327
|
+
self.discrete_limit = discrete_limit
|
|
328
|
+
self.n_jobs = n_jobs
|
|
329
|
+
self.verbose = verbose
|
|
330
|
+
|
|
331
|
+
def _validate_parameters(self, n_samples, n_features):
|
|
332
|
+
"""Validate all user-provided parameters."""
|
|
333
|
+
# Backend check
|
|
334
|
+
if self.backend not in ["auto", "gpu", "cpu"]:
|
|
335
|
+
raise ValueError("backend must be one of 'auto', 'gpu', or 'cpu'")
|
|
336
|
+
|
|
337
|
+
# Sample count check
|
|
338
|
+
if n_samples < 2:
|
|
339
|
+
raise ValueError(
|
|
340
|
+
f"MultiSURF requires at least 2 samples, but got n_samples = {n_samples}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# n_features_to_select check (handles both int and float)
|
|
345
|
+
if isinstance(self.n_features_to_select, float):
|
|
346
|
+
if not 0.0 < self.n_features_to_select <= 1.0:
|
|
347
|
+
raise ValueError(
|
|
348
|
+
"If n_features_to_select is a float, it must be in (0, 1]."
|
|
349
|
+
)
|
|
350
|
+
n_select = max(1, int(self.n_features_to_select * n_features))
|
|
351
|
+
elif isinstance(self.n_features_to_select, int):
|
|
352
|
+
if not 0 < self.n_features_to_select <= n_features:
|
|
353
|
+
raise ValueError(
|
|
354
|
+
f"If n_features_to_select is an int ({self.n_features_to_select}), "
|
|
355
|
+
f"it must be > 0 and <= n_features ({n_features})."
|
|
356
|
+
)
|
|
357
|
+
n_select = self.n_features_to_select
|
|
358
|
+
else:
|
|
359
|
+
raise TypeError("n_features_to_select must be an int or a float.")
|
|
360
|
+
|
|
361
|
+
return n_select
|
|
362
|
+
|
|
363
|
+
def fit(self, x: np.ndarray, y: np.ndarray):
|
|
364
|
+
"""
|
|
365
|
+
Calculates feature importances using the MultiSURF algorithm on a GPU/CPU.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
x : array-like of shape (n_samples, n_features)
|
|
370
|
+
The training input samples.
|
|
371
|
+
y : array-like of shape (n_samples,)
|
|
372
|
+
The target values (class labels).
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
self : object
|
|
377
|
+
Returns the instance itself.
|
|
378
|
+
"""
|
|
379
|
+
x, y = validate_data(
|
|
380
|
+
self, x, y, y_numeric=True, dtype=np.float64, ensure_2d=True,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
self.n_features_in_ = x.shape[1]
|
|
384
|
+
n_samples = x.shape[0]
|
|
385
|
+
|
|
386
|
+
n_select = self._validate_parameters(n_samples, self.n_features_in_)
|
|
387
|
+
|
|
388
|
+
if self.backend == "auto":
|
|
389
|
+
if cuda.is_available():
|
|
390
|
+
self.effective_backend_ = "gpu"
|
|
391
|
+
else:
|
|
392
|
+
self.effective_backend_ = "cpu"
|
|
393
|
+
elif self.backend == "gpu":
|
|
394
|
+
if not cuda.is_available():
|
|
395
|
+
raise RuntimeError(
|
|
396
|
+
"backend='gpu' was selected, but no compatible "
|
|
397
|
+
"NVIDIA GPU was found or CUDA toolkit is not installed."
|
|
398
|
+
)
|
|
399
|
+
self.effective_backend_ = "gpu"
|
|
400
|
+
else:
|
|
401
|
+
self.effective_backend_ = "cpu"
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
feature_ranges = _compute_ranges(x)
|
|
405
|
+
|
|
406
|
+
feature_ranges[feature_ranges == 0] = 1
|
|
407
|
+
recip_full = (1.0 / feature_ranges).astype(np.float32)
|
|
408
|
+
|
|
409
|
+
all_feature_indices = np.arange(self.n_features_in_, dtype=np.int64)
|
|
410
|
+
|
|
411
|
+
is_discrete = np.zeros(self.n_features_in_, dtype=bool)
|
|
412
|
+
self.is_discrete_ = is_discrete
|
|
413
|
+
for f in range(self.n_features_in_):
|
|
414
|
+
if np.unique(x[:, f]).size <= self.discrete_limit: # Define discrete_limit
|
|
415
|
+
is_discrete[f] = True
|
|
416
|
+
|
|
417
|
+
if self.effective_backend_ == "gpu":
|
|
418
|
+
x_d = cuda.to_device(x)
|
|
419
|
+
y_d = cuda.to_device(y.astype(np.int32))
|
|
420
|
+
recip_full_d = cuda.to_device(recip_full)
|
|
421
|
+
if self.verbose and self.use_star:
|
|
422
|
+
print("Running MultiSURF* on the GPU now...")
|
|
423
|
+
elif self.verbose:
|
|
424
|
+
print("Running MultiSURF on the GPU now...")
|
|
425
|
+
scores = _multisurf_gpu_host_caller(
|
|
426
|
+
x_d, y_d, recip_full_d, all_feature_indices, self.use_star, is_discrete
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
if self.verbose and self.use_star:
|
|
430
|
+
print("Running MultiSURF* on the CPU now...")
|
|
431
|
+
elif self.verbose:
|
|
432
|
+
print("Running MultiSURF on the CPU now...")
|
|
433
|
+
scores = _multisurf_cpu_host_caller(
|
|
434
|
+
x, y, recip_full, all_feature_indices, self.use_star, is_discrete, self.n_jobs
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
self.feature_importances_ = scores
|
|
438
|
+
self.top_features_ = np.argsort(scores)[::-1][:n_select]
|
|
439
|
+
return self
|
|
440
|
+
|
|
441
|
+
def transform(self, x: np.ndarray) -> np.ndarray:
|
|
442
|
+
"""
|
|
443
|
+
Reduces x to the selected features.
|
|
444
|
+
|
|
445
|
+
Parameters
|
|
446
|
+
----------
|
|
447
|
+
x : array-like of shape (n_samples, n_features)
|
|
448
|
+
The input samples to transform.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
x_new : ndarray of shape (n_samples, n_features_to_select)
|
|
453
|
+
The input samples with only the selected features.
|
|
454
|
+
"""
|
|
455
|
+
check_is_fitted(self)
|
|
456
|
+
x = validate_data(
|
|
457
|
+
self, x,
|
|
458
|
+
reset=False,
|
|
459
|
+
ensure_2d=True,
|
|
460
|
+
dtype=[np.float64, np.float32]
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if x.shape[1] != self.n_features_in_:
|
|
464
|
+
raise ValueError(
|
|
465
|
+
f"x has {x.shape[1]} features, but {self.__class__.__name__} "
|
|
466
|
+
f"was trained with {self.n_features_in_} features."
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
return x[:, self.top_features_]
|
|
470
|
+
|
|
471
|
+
def fit_transform(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
|
|
472
|
+
"""
|
|
473
|
+
Fit to data, then transform it.
|
|
474
|
+
|
|
475
|
+
A convenience method that fits the model and applies the transformation
|
|
476
|
+
to the same data.
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
x : array-like of shape (n_samples, n_features)
|
|
481
|
+
The training input samples.
|
|
482
|
+
y : array-like of shape (n_samples,)
|
|
483
|
+
The target values (class labels).
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
x_new : ndarray of shape (n_samples, n_features_to_select)
|
|
488
|
+
The transformed input samples.
|
|
489
|
+
"""
|
|
490
|
+
self.fit(x, y)
|
|
491
|
+
return self.transform(x)
|