SKSurrogate 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,457 @@
1
+ """
2
+ Hilbert Space based regression
3
+ ==================================
4
+ """
5
+
6
+ Infinitesimal = 1e-7
7
+
8
+
9
+ class Error(Exception):
10
+ r"""
11
+ Generic errors that may occur in the course of a run.
12
+ """
13
+
14
+ def __init__(self, *args):
15
+ super(Error, self).__init__(*args)
16
+
17
+
18
+ class Measure(object):
19
+ r"""
20
+ Constructs a measure :math:`\mu` based on `density` and `domain`.
21
+
22
+ :param density: the density over the domain:
23
+ + if none is given, it assumes uniform distribution
24
+
25
+ + if a callable `h` is given, then :math:`d\mu=h(x)dx`
26
+
27
+ + if a dictionary is given, then :math:`\mu=\sum w_x\delta_x` a discrete measure.
28
+ The points :math:`x` are the keys of the dictionary (tuples) and the weights :math:`w_x` are the values.
29
+ :param domain: if `density` is a dictionary, it will be set by its keys. If callable, then `domain` must be a list
30
+ of tuples defining the domain's box. If None is given, it will be set to :math:`[-1, 1]^n`
31
+ """
32
+
33
+ def __init__(self, density=None, domain=None):
34
+
35
+ # set the density
36
+ if density is None:
37
+ self.density = lambda x: 1.0
38
+ elif callable(density):
39
+ self.density = density
40
+ elif isinstance(density, dict):
41
+ self.density = density # lambda x: density[x] if x in density else 0.
42
+ else:
43
+ raise Error(
44
+ "The `density` must be either a callable or a dictionary of real numbers."
45
+ )
46
+ # check and set the domain
47
+ self.continuous = True
48
+ self.dim = 0
49
+ if isinstance(domain, list):
50
+ self.dim = len(domain)
51
+ for intrvl in domain:
52
+ if (not isinstance(intrvl, (list, tuple))) or (len(intrvl) != 2):
53
+ raise Error("`domain` should be a list of 2-tuples.")
54
+ self.supp = domain
55
+ elif isinstance(density, dict):
56
+ self.supp = density.keys()
57
+ self.continuous = False
58
+ else:
59
+ raise Error("No domain is specified.")
60
+
61
+ def integral(self, f):
62
+ r"""
63
+ Calculates :math:`\int_{domain} fd\mu`.
64
+
65
+ :param f: the integrand
66
+ :return: the value of the integral
67
+ """
68
+ from types import FunctionType
69
+
70
+ m = 0.0
71
+ if not isinstance(
72
+ f, (dict, FunctionType)
73
+ ): # if type(f) not in [dict, FunctionType]:
74
+ raise Error("The integrand must be a `function` or a `dict`")
75
+ if isinstance(f, dict):
76
+ fn = lambda x: f[x] if x in f else 0.0
77
+ else:
78
+ fn = f
79
+ if self.continuous:
80
+ from scipy import integrate
81
+
82
+ fw = lambda *x: fn(*x) * self.density(*x)
83
+ m = integrate.nquad(fw, self.supp)[0]
84
+ else:
85
+ for p in self.supp:
86
+ # print(p, self.density[p], fn)
87
+ # print(p, self.density[p], fn(p))
88
+ m += self.density[p] * fn(p)
89
+ return m
90
+
91
+ def norm(self, p, f):
92
+ r"""
93
+ Computes the norm-`p` of the `f` with respect to the current measure,
94
+ i.e., :math:`(\int_{domain}|f|^p d\mu)^{1/p}`.
95
+
96
+ :param p: a positive real number
97
+ :param f: the function whose norm is desired.
98
+ :return: :math:`\|f\|_{p, \mu}`
99
+ """
100
+ absfp = lambda *x: pow(abs(f(*x)), p)
101
+ return pow(self.integral(absfp), 1.0 / p)
102
+
103
+
104
+ class FunctionBasis(object):
105
+ """
106
+ This class generates two typical basis of functions: Polynomials and Trigonometric
107
+ """
108
+
109
+ def __init__(self):
110
+ pass
111
+
112
+ @staticmethod
113
+ def Poly(n, deg):
114
+ """
115
+ Returns a basis consisting of polynomials in `n` variables of degree at most `deg`.
116
+
117
+ :param n: number of variables
118
+ :param deg: highest degree of polynomials in the basis
119
+ :return: the raw basis consists of polynomials of degrees up to `n`
120
+ """
121
+ from itertools import product
122
+ from numpy import prod
123
+
124
+ B = []
125
+ for o in product(range(deg + 1), repeat=n):
126
+ if sum(o) <= deg:
127
+ B.append(lambda x, e=o: prod([x[i] ** e[i] for i in range(n)]))
128
+ return B
129
+
130
+ @staticmethod
131
+ def Fourier(n, deg, l=1.0):
132
+ """
133
+ Returns the Fourier basis of degree `deg` in `n` variables with period `l`
134
+
135
+ :param n: number of variables
136
+ :param deg: the maximum degree of trigonometric combinations in the basis
137
+ :param l: the period
138
+ :return: the raw basis consists of trigonometric functions of degrees up to `n`
139
+ """
140
+
141
+ from numpy import sin, cos, prod
142
+ from itertools import product
143
+
144
+ B = [lambda x: 1.0]
145
+ E = list(product([0, 1], repeat=n))
146
+ RawCoefs = list(product(range(deg + 1), repeat=n))
147
+ Coefs = set()
148
+ for prt in RawCoefs:
149
+ p_ = list(prt)
150
+ p_.sort()
151
+ Coefs.add(tuple(p_))
152
+ for o in Coefs:
153
+ if (sum(o) <= deg) and (sum(o) > 0):
154
+ for ex in E:
155
+ if sum(ex) >= 0:
156
+ f_ = lambda x, o_=o, ex_=ex: prod(
157
+ [
158
+ sin(o_[i] * x[i] / l) ** ex_[i]
159
+ * cos(o_[i] * x[i] / l) ** (1 - ex_[i])
160
+ if o_[i] > 0
161
+ else 1.0
162
+ for i in range(n)
163
+ ]
164
+ )
165
+ B.append(f_)
166
+ return B
167
+
168
+
169
+ class FunctionSpace(object):
170
+ r"""
171
+ A class tha facilitates a few types of computations over function spaces of type :math:`L_2(X, \mu)`
172
+
173
+ :param dim: the dimension of 'X' (default: 1)
174
+ :param measure: an object of type `Measure` representing :math:`\mu`
175
+ :param basis: a finite basis of functions to construct a subset of :math:`L_2(X, \mu)`
176
+ """
177
+ dim = 1 # type: int
178
+
179
+ def __init__(self, dim=1, measure=None, basis=None):
180
+ self.dim = int(dim)
181
+ if (measure is not None) and (isinstance(measure, Measure)):
182
+ self.measure = measure
183
+ else:
184
+ # default measure is set to be the Lebesgue measure on [0, 1]^dim
185
+ D = [(0.0, 1.0) for _ in range(self.dim)]
186
+ self.measure = Measure(domain=D)
187
+ if basis is None:
188
+ # default basis is linear
189
+ from numpy import array
190
+
191
+ B = [lambda x: 1.0]
192
+ for i in range(self.dim):
193
+ B.append(lambda x, i_=i: x[i_] if isinstance(x, array) else x)
194
+ self.base = B
195
+ else:
196
+ self.base = basis
197
+ self.OrthBase = []
198
+ self.Gram = None
199
+
200
+ def inner(self, f, g):
201
+ r"""
202
+ Computes the inner product of the two parameters with respect to
203
+ the measure `measure`, i.e., :math:`\int_Xf\cdot g d\mu`.
204
+
205
+ :param f: callable
206
+ :param g: callable
207
+ :return: the quantity of :math:`\int_Xf\cdot g d\mu`
208
+ """
209
+ fn = lambda x, f_=f, g_=g: f_(x) * g_(x)
210
+ return self.measure.integral(fn)
211
+
212
+ def project(self, f, g):
213
+ r"""
214
+ Finds the projection of `f` on `g` with respect to the inner
215
+ product induced by the measure `measure`.
216
+
217
+ :param f: callable
218
+ :param g: callable
219
+ :return: the quantity of :math:`\frac{\langle f, g\rangle}{\|g\|_2}g`
220
+ """
221
+ a = self.inner(f, g)
222
+ b = self.inner(g, g)
223
+ return lambda x: a * g(x) / b
224
+
225
+ def GramMat(self):
226
+ from numpy import zeros
227
+
228
+ N = len(self.base)
229
+ cfs = zeros((N, N))
230
+ for i in range(N):
231
+ for j in range(i, N):
232
+ cf = self.inner(self.base[i], self.base[j])
233
+ cfs[i][j] = cf
234
+ cfs[j][i] = cf
235
+ self.Gram = cfs
236
+
237
+ def minor_gram(self, i):
238
+ if self.Gram is None:
239
+ self.GramMat()
240
+ return self.Gram[:i + 1, :i + 1]
241
+
242
+ def minor(self, i, j):
243
+ from numpy import delete
244
+ from numpy.linalg import det
245
+
246
+ if j == 1:
247
+ return 1.0
248
+ return det(delete(self.Gram[:j - 1, :j], i, axis=1))
249
+
250
+ def FormBasis(self):
251
+ """
252
+ Call this method to generate the orthogonal basis corresponding
253
+ to the given basis.
254
+ The result will be stored in a property called ``OrthBase`` which
255
+ is a list of function that are orthogonal to each other with
256
+ respect to the measure ``measure`` over the given range ``domain``.
257
+ """
258
+ from numpy.linalg import det
259
+ from numpy import sqrt
260
+
261
+ N = len(self.base)
262
+ GramDets = [1.0] + [det(self.minor_gram(i)) for i in range(N)]
263
+ B = []
264
+ D = []
265
+ for j in range(1, N + 1):
266
+ j_ = j
267
+ cf = [
268
+ (-1) ** (i + j - 1)
269
+ * self.minor(i, j_)
270
+ / sqrt(GramDets[j_ - 1] * GramDets[j_])
271
+ for i in range(j_)
272
+ ]
273
+ # print(j_, cf, [self.base[i](2.) for i in range(j_)])
274
+ B.append(lambda x: sum([cf[i] * self.base[i](x) for i in range(j_)]))
275
+ D.append(cf)
276
+ self.OrthBase = []
277
+ for i in range(len(D)):
278
+ fn = lambda x, i_=i: sum(
279
+ [D[i_][_j] * self.base[_j](x) for _j in range(len(D[i_]))]
280
+ )
281
+ self.OrthBase.append(fn)
282
+
283
+ def Series(self, f):
284
+ r"""
285
+ Given a function `f`, this method finds and returns the
286
+ coefficients of the series that approximates `f` as a
287
+ linear combination of the elements of the orthogonal basis :math:`B`.
288
+ In symbols :math:`\sum_{b\in B}\langle f, b\rangle b`.
289
+
290
+ :return: the list of coefficients :math:`\langle f, b\rangle` for :math:`b\in B`
291
+ """
292
+ cfs = []
293
+ for b in self.OrthBase:
294
+ cfs.append(self.inner(f, b))
295
+ return cfs
296
+
297
+
298
+ class Regression(object):
299
+ """
300
+ Given a set of points, i.e., a list of tuples of the equal lengths `P`, this class computes the best approximation
301
+ of a function that fits the data, in the following sense:
302
+
303
+ + if no extra parameters is provided, meaning that an object is initiated like ``R = Regression(P)`` then
304
+ calling ``R.fit()`` returns the linear regression that fits the data.
305
+ + if at initiation the parameter `deg=n` is set, then ``R.fit()`` returns the polynomial regression of
306
+ degree `n`.
307
+ + if a basis of functions provided by means of an `OrthSystem` object (``R.SetOrthSys(orth)``) then
308
+ calling ``R.fit()`` returns the best approximation that can be found using the basic functions of
309
+ the `orth` object.
310
+
311
+ :param points: a list of points to be fitted or a callable to be approximated
312
+ :param dim: dimension of the domain
313
+ """
314
+
315
+ def __init__(self, points, dim=None):
316
+ from numpy import array, ndarray
317
+
318
+ self.Points = None
319
+ if isinstance(points, (ndarray, list, array)):
320
+ self.Points = list(points)
321
+ self.dim = len(points[0]) - 1
322
+ supp = {}
323
+ for p in points:
324
+ supp[tuple(p[:-1])] = 1.0
325
+ self.meas = Measure(supp)
326
+ self.f = lambda x: sum(
327
+ [
328
+ p_[-1] * (1 * (abs(x - array(p_[:-1])) < 1.0e-4)).min()
329
+ for p_ in points
330
+ ]
331
+ )
332
+ elif callable(points):
333
+ if dim is None:
334
+ raise Error("The dimension can not be determined")
335
+ else:
336
+ self.dim = dim
337
+ self.f = points
338
+ self.meas = Measure(domain=[(-1.0, 1.0) for _ in range(self.dim)])
339
+ self.Orth = FunctionSpace(dim=self.dim, measure=self.meas)
340
+ # self.Orth.FormBasis()
341
+
342
+ def SetMeasure(self, meas):
343
+ """
344
+ Sets the default measure for approximation.
345
+
346
+ :param meas: a measure.Measure object
347
+ :return: None
348
+ """
349
+ if not isinstance(meas, Measure):
350
+ raise AssertionError("SetMeasure accepts a NpyProximation.Measure object.")
351
+ self.meas = meas
352
+
353
+ def SetFuncSpc(self, sys):
354
+ """
355
+ Sets the bases of the orthogonal basis
356
+
357
+ :param sys: `orthsys.OrthSystem` object.
358
+ :return: None
359
+
360
+ .. Note::
361
+ For technical reasons, the measure needs to be given via `SetMeasure` method. Otherwise, the Lebesque
362
+ measure on :math:`[-1, 1]^n` is assumed.
363
+ """
364
+ if self.dim != sys.dim:
365
+ raise AssertionError(
366
+ "Dimensions of points and the orthogonal system do not match."
367
+ )
368
+ sys.measure = self.meas
369
+ self.Orth = sys
370
+ self.Orth.FormBasis()
371
+
372
+ def fit(self):
373
+ """
374
+ Fits the best curve based on the optional provided orthogonal basis.
375
+ If no basis is provided, it fits a polynomial of a given degree (at initiation)
376
+ :return: The fit.
377
+ """
378
+ coefs = self.Orth.Series(self.f)
379
+ aprx = lambda x: sum(
380
+ [
381
+ coefs[i] * self.Orth.OrthBase[i](x)
382
+ for i in range(len(self.Orth.OrthBase))
383
+ ]
384
+ )
385
+ return aprx
386
+
387
+
388
+ try:
389
+ from sklearn.base import BaseEstimator, RegressorMixin
390
+ except ModuleNotFoundError:
391
+ BaseEstimator = type("BaseEstimator", (object,), dict())
392
+ RegressorMixin = type("RegressorMixin", (object,), dict())
393
+
394
+
395
+ class HilbertRegressor(BaseEstimator, RegressorMixin):
396
+ r"""
397
+ Regression using Hilbert Space techniques Scikit-Learn style.
398
+
399
+ :param deg: int, default=3
400
+ The degree of polynomial regression. Only used if `base` is `None`
401
+ :param base: list, default = None
402
+ a list of function to form an orthogonal function basis
403
+ :param meas: NpyProximation.Measure, default = None
404
+ the measure to form the :math:`L_2(\mu)` space. If `None` a discrete measure will be constructed based
405
+ on `fit` inputs
406
+ :param fspace: NpyProximation.FunctionBasis, default = None
407
+ the function subspace of :math:`L_2(\mu)`, if `None` it will be initiated according to `self.meas`
408
+ """
409
+
410
+ def __init__(self, deg=3, base=None, meas=None, fspace=None):
411
+ self.deg = deg
412
+ self.meas = meas
413
+ self.base = base
414
+ self.fspace = fspace
415
+ self.Regressor = None
416
+ self.dim = 0
417
+ self.apprx = None
418
+
419
+ def fit(self, X, y):
420
+ """
421
+
422
+ :param X: Training data
423
+ :param y: Target values
424
+ :return: `self`
425
+ """
426
+ from numpy import concatenate
427
+
428
+ if len(X.shape) != 2:
429
+ X = X.reshape(X.shape[0], 1)
430
+ points = concatenate((X, y.reshape(y.shape[0], 1)), axis=1)
431
+ self.Regressor = Regression(points)
432
+ self.dim = X[0].shape[0]
433
+ if self.fspace is not None:
434
+ self.Regressor.SetFuncSpc(self.fspace)
435
+ else:
436
+ bs = FunctionBasis()
437
+ B = bs.Poly(n=self.dim, deg=self.deg) if self.base is None else self.base
438
+ self.fspace = FunctionSpace(dim=self.dim, basis=B)
439
+ self.Regressor.SetFuncSpc(self.fspace)
440
+ if self.meas is not None:
441
+ self.Regressor.SetMeasure(self.meas)
442
+ self.apprx = self.Regressor.fit()
443
+
444
+ return self
445
+
446
+ def predict(self, X):
447
+ """
448
+ Predict using the Hilbert regression method
449
+
450
+ :param X: Samples
451
+ :return: Returns predicted values
452
+ """
453
+ from numpy import array
454
+
455
+ if len(X.shape) != 2:
456
+ X = X.reshape(X.shape[0], 1)
457
+ return array([self.apprx(x) for x in X])
@@ -0,0 +1,110 @@
1
+ from .structsearch import (
2
+ BoxSample,
3
+ SphereSample,
4
+ SurrogateSearch,
5
+ Real,
6
+ Integer,
7
+ Categorical,
8
+ HDReal,
9
+ SurrogateRandomCV,
10
+ )
11
+ from .NpyProximation import (
12
+ Infinitesimal,
13
+ Measure,
14
+ FunctionBasis,
15
+ FunctionSpace,
16
+ Regression,
17
+ HilbertRegressor,
18
+ )
19
+ from .sensapprx import SensAprx, CorrelationThreshold
20
+ from .aml import Words, StackingEstimator, AML
21
+ from .eoa import EOA, UniformRand, MaxGenTermination, UniformCrossover, Elites, Mutation
22
+ from .mltrace import (
23
+ STANDARD_CV_SPLITTERS,
24
+ build_cv,
25
+ cv_param_defs,
26
+ cv_to_spec,
27
+ default_cv_spec,
28
+ mltrack,
29
+ np2df,
30
+ )
31
+ from .DataProcess import DataPreprocess
32
+ from .ci import BundleQualityGateError, assert_bundle_quality, check_bundle_quality
33
+ from .deployment import DeploymentApprovalGate
34
+ from .execution import DaskExecutionBackend, ExecutionBackend, LocalProcessExecutionBackend
35
+ from .modelbundle import ModelBundle, ModelRegistry, export_mlflow, load_bundle, save_bundle
36
+ from .inference import SchemaValidationError, create_app, predict_batch, serve
37
+ from .monitoring import (
38
+ InferenceMonitor,
39
+ delayed_label_performance,
40
+ drift_report,
41
+ fairness_report,
42
+ loss_report,
43
+ prediction_distribution_report,
44
+ sensitive_feature_report,
45
+ subgroup_performance_report,
46
+ )
47
+ from .retraining import RetrainingJob
48
+ from .synthdat import SynthData
49
+
50
+ __all__ = [
51
+ "AML",
52
+ "BoxSample",
53
+ "BundleQualityGateError",
54
+ "Categorical",
55
+ "CorrelationThreshold",
56
+ "build_cv",
57
+ "cv_param_defs",
58
+ "cv_to_spec",
59
+ "default_cv_spec",
60
+ "DataPreprocess",
61
+ "DeploymentApprovalGate",
62
+ "DaskExecutionBackend",
63
+ "ExecutionBackend",
64
+ "EOA",
65
+ "Elites",
66
+ "FunctionBasis",
67
+ "FunctionSpace",
68
+ "HDReal",
69
+ "HilbertRegressor",
70
+ "Infinitesimal",
71
+ "Integer",
72
+ "MaxGenTermination",
73
+ "Measure",
74
+ "Mutation",
75
+ "Real",
76
+ "Regression",
77
+ "RetrainingJob",
78
+ "STANDARD_CV_SPLITTERS",
79
+ "SensAprx",
80
+ "SphereSample",
81
+ "StackingEstimator",
82
+ "SynthData",
83
+ "SurrogateRandomCV",
84
+ "SurrogateSearch",
85
+ "UniformCrossover",
86
+ "UniformRand",
87
+ "Words",
88
+ "mltrack",
89
+ "assert_bundle_quality",
90
+ "check_bundle_quality",
91
+ "ModelBundle",
92
+ "ModelRegistry",
93
+ "export_mlflow",
94
+ "predict_batch",
95
+ "SchemaValidationError",
96
+ "create_app",
97
+ "serve",
98
+ "drift_report",
99
+ "fairness_report",
100
+ "prediction_distribution_report",
101
+ "delayed_label_performance",
102
+ "loss_report",
103
+ "sensitive_feature_report",
104
+ "subgroup_performance_report",
105
+ "InferenceMonitor",
106
+ "LocalProcessExecutionBackend",
107
+ "np2df",
108
+ "load_bundle",
109
+ "save_bundle",
110
+ ]