mumpy-toolkit 0.2.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.
- mumpy/__init__.py +128 -0
- mumpy/_core.py +218 -0
- mumpy/_math.py +283 -0
- mumpy/_parallel.py +142 -0
- mumpy/db.py +289 -0
- mumpy/fft.py +132 -0
- mumpy/frame.py +428 -0
- mumpy/io.py +196 -0
- mumpy/linalg.py +62 -0
- mumpy/metrics.py +187 -0
- mumpy/ml.py +443 -0
- mumpy/nn.py +278 -0
- mumpy/preprocessing.py +259 -0
- mumpy/random.py +99 -0
- mumpy/stats.py +122 -0
- mumpy/utils.py +105 -0
- mumpy/viz.py +81 -0
- mumpy_toolkit-0.2.0.dist-info/METADATA +200 -0
- mumpy_toolkit-0.2.0.dist-info/RECORD +22 -0
- mumpy_toolkit-0.2.0.dist-info/WHEEL +5 -0
- mumpy_toolkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- mumpy_toolkit-0.2.0.dist-info/top_level.txt +1 -0
mumpy/nn.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""mumpy.nn: tiny deep-learning toolkit in pure numpy.
|
|
2
|
+
|
|
3
|
+
Layers: Dense / ReLU / Sigmoid / Tanh / Softmax / Dropout / BatchNorm-lite
|
|
4
|
+
Losses: MSE / BCE / CrossEntropy
|
|
5
|
+
Optimizers: SGD (+momentum) / Adam
|
|
6
|
+
Model: Sequential with fit/predict/save/load
|
|
7
|
+
|
|
8
|
+
from mumpy import nn
|
|
9
|
+
model = nn.Sequential([nn.Dense(4, 16), nn.ReLU(), nn.Dense(16, 1)])
|
|
10
|
+
model.fit(X, y, epochs=200, lr=0.01, verbose=True)
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Dense", "ReLU", "Sigmoid", "Tanh", "Softmax", "Dropout",
|
|
18
|
+
"MSELoss", "BCELoss", "CrossEntropyLoss",
|
|
19
|
+
"SGD", "Adam", "Sequential",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Dense:
|
|
24
|
+
def __init__(self, in_features, out_features, seed=0):
|
|
25
|
+
rng = np.random.default_rng(seed)
|
|
26
|
+
self.W = rng.normal(0, np.sqrt(2 / in_features), (in_features, out_features))
|
|
27
|
+
self.b = np.zeros(out_features)
|
|
28
|
+
self.dW = np.zeros_like(self.W)
|
|
29
|
+
self.db = np.zeros_like(self.b)
|
|
30
|
+
|
|
31
|
+
def forward(self, x):
|
|
32
|
+
self.x = x
|
|
33
|
+
return x @ self.W + self.b
|
|
34
|
+
|
|
35
|
+
def backward(self, grad):
|
|
36
|
+
self.dW = self.x.T @ grad
|
|
37
|
+
self.db = grad.sum(0)
|
|
38
|
+
return grad @ self.W.T
|
|
39
|
+
|
|
40
|
+
def params(self):
|
|
41
|
+
return [(self.W, self.dW), (self.b, self.db)]
|
|
42
|
+
|
|
43
|
+
def __call__(self, x):
|
|
44
|
+
return self.forward(x)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ReLU:
|
|
48
|
+
def forward(self, x):
|
|
49
|
+
self.x = x
|
|
50
|
+
return np.maximum(x, 0)
|
|
51
|
+
|
|
52
|
+
def backward(self, g):
|
|
53
|
+
return g * (self.x > 0)
|
|
54
|
+
|
|
55
|
+
def params(self):
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
def __call__(self, x):
|
|
59
|
+
return self.forward(x)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Sigmoid:
|
|
63
|
+
def forward(self, x):
|
|
64
|
+
self.o = 1 / (1 + np.exp(-np.clip(x, -30, 30)))
|
|
65
|
+
return self.o
|
|
66
|
+
|
|
67
|
+
def backward(self, g):
|
|
68
|
+
return g * self.o * (1 - self.o)
|
|
69
|
+
|
|
70
|
+
def params(self):
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
def __call__(self, x):
|
|
74
|
+
return self.forward(x)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Tanh:
|
|
78
|
+
def forward(self, x):
|
|
79
|
+
self.o = np.tanh(x)
|
|
80
|
+
return self.o
|
|
81
|
+
|
|
82
|
+
def backward(self, g):
|
|
83
|
+
return g * (1 - self.o ** 2)
|
|
84
|
+
|
|
85
|
+
def params(self):
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
def __call__(self, x):
|
|
89
|
+
return self.forward(x)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Softmax:
|
|
93
|
+
def forward(self, x):
|
|
94
|
+
x = x - x.max(1, keepdims=True)
|
|
95
|
+
e = np.exp(x)
|
|
96
|
+
self.o = e / e.sum(1, keepdims=True)
|
|
97
|
+
return self.o
|
|
98
|
+
|
|
99
|
+
def backward(self, g):
|
|
100
|
+
# combined with CrossEntropy upstream usually passes (p - y)/n
|
|
101
|
+
return g
|
|
102
|
+
|
|
103
|
+
def params(self):
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
def __call__(self, x):
|
|
107
|
+
return self.forward(x)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Dropout:
|
|
111
|
+
def __init__(self, p=0.2, seed=0):
|
|
112
|
+
self.p = p
|
|
113
|
+
self.rng = np.random.default_rng(seed)
|
|
114
|
+
self.training = True
|
|
115
|
+
|
|
116
|
+
def forward(self, x):
|
|
117
|
+
if not self.training or self.p == 0:
|
|
118
|
+
return x
|
|
119
|
+
self.mask = (self.rng.random(x.shape) > self.p) / (1 - self.p)
|
|
120
|
+
return x * self.mask
|
|
121
|
+
|
|
122
|
+
def backward(self, g):
|
|
123
|
+
return g * self.mask if self.training and self.p else g
|
|
124
|
+
|
|
125
|
+
def params(self):
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
def __call__(self, x):
|
|
129
|
+
return self.forward(x)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class MSELoss:
|
|
133
|
+
def forward(self, pred, target):
|
|
134
|
+
self.pred, self.target = pred, target
|
|
135
|
+
return float(np.mean((pred - target) ** 2))
|
|
136
|
+
|
|
137
|
+
def backward(self):
|
|
138
|
+
n = self.pred.size
|
|
139
|
+
return 2 * (self.pred - self.target) / n
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BCELoss:
|
|
143
|
+
def forward(self, pred, target, eps=1e-12):
|
|
144
|
+
self.pred = np.clip(pred, eps, 1 - eps)
|
|
145
|
+
self.target = target
|
|
146
|
+
return float(-np.mean(target * np.log(self.pred) + (1 - target) * np.log(1 - self.pred)))
|
|
147
|
+
|
|
148
|
+
def backward(self):
|
|
149
|
+
n = self.pred.size
|
|
150
|
+
p, t = self.pred, self.target
|
|
151
|
+
return (-(t / p) + (1 - t) / (1 - p)) / n
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class CrossEntropyLoss:
|
|
155
|
+
def forward(self, logits, target):
|
|
156
|
+
# logits: (n, K), target: class indices
|
|
157
|
+
z = logits - logits.max(1, keepdims=True)
|
|
158
|
+
e = np.exp(z)
|
|
159
|
+
self.probs = e / e.sum(1, keepdims=True)
|
|
160
|
+
self.target = np.asanyarray(target).ravel().astype(int)
|
|
161
|
+
n = len(self.target)
|
|
162
|
+
return float(-np.log(self.probs[np.arange(n), self.target] + 1e-12).mean())
|
|
163
|
+
|
|
164
|
+
def backward(self):
|
|
165
|
+
n = len(self.target)
|
|
166
|
+
g = self.probs.copy()
|
|
167
|
+
g[np.arange(n), self.target] -= 1
|
|
168
|
+
return g / n
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class SGD:
|
|
172
|
+
def __init__(self, lr=0.01, momentum=0.0):
|
|
173
|
+
self.lr = lr
|
|
174
|
+
self.momentum = momentum
|
|
175
|
+
self.vel = {}
|
|
176
|
+
|
|
177
|
+
def step(self, layers):
|
|
178
|
+
for li, layer in enumerate(layers):
|
|
179
|
+
for pi, (p, g) in enumerate(layer.params()):
|
|
180
|
+
key = (li, pi)
|
|
181
|
+
if self.momentum:
|
|
182
|
+
v = self.vel.get(key, np.zeros_like(p))
|
|
183
|
+
v = self.momentum * v + g
|
|
184
|
+
self.vel[key] = v
|
|
185
|
+
p -= self.lr * v
|
|
186
|
+
else:
|
|
187
|
+
p -= self.lr * g
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Adam:
|
|
191
|
+
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
|
|
192
|
+
self.lr = lr
|
|
193
|
+
self.beta1 = beta1
|
|
194
|
+
self.beta2 = beta2
|
|
195
|
+
self.eps = eps
|
|
196
|
+
self.m = {}
|
|
197
|
+
self.v = {}
|
|
198
|
+
self.t = 0
|
|
199
|
+
|
|
200
|
+
def step(self, layers):
|
|
201
|
+
self.t += 1
|
|
202
|
+
for li, layer in enumerate(layers):
|
|
203
|
+
for pi, (p, g) in enumerate(layer.params()):
|
|
204
|
+
key = (li, pi)
|
|
205
|
+
m = self.m.get(key, np.zeros_like(p))
|
|
206
|
+
v = self.v.get(key, np.zeros_like(p))
|
|
207
|
+
m = self.beta1 * m + (1 - self.beta1) * g
|
|
208
|
+
v = self.beta2 * v + (1 - self.beta2) * (g ** 2)
|
|
209
|
+
self.m[key], self.v[key] = m, v
|
|
210
|
+
mh = m / (1 - self.beta1 ** self.t)
|
|
211
|
+
vh = v / (1 - self.beta2 ** self.t)
|
|
212
|
+
p -= self.lr * mh / (np.sqrt(vh) + self.eps)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class Sequential:
|
|
216
|
+
def __init__(self, layers):
|
|
217
|
+
self.layers = layers
|
|
218
|
+
|
|
219
|
+
def forward(self, x):
|
|
220
|
+
for l in self.layers:
|
|
221
|
+
x = l.forward(x)
|
|
222
|
+
return x
|
|
223
|
+
|
|
224
|
+
def predict(self, X):
|
|
225
|
+
for l in self.layers:
|
|
226
|
+
if isinstance(l, Dropout):
|
|
227
|
+
l.training = False
|
|
228
|
+
out = self.forward(np.asanyarray(X, dtype=float))
|
|
229
|
+
for l in self.layers:
|
|
230
|
+
if isinstance(l, Dropout):
|
|
231
|
+
l.training = True
|
|
232
|
+
return out
|
|
233
|
+
|
|
234
|
+
def fit(self, X, y, epochs=100, batch_size=None, lr=0.01, optimizer=None,
|
|
235
|
+
loss="mse", verbose=False, seed=0, shuffle=True):
|
|
236
|
+
X = np.asanyarray(X, dtype=float)
|
|
237
|
+
y = np.asanyarray(y)
|
|
238
|
+
rng = np.random.default_rng(seed)
|
|
239
|
+
opt = optimizer or Adam(lr)
|
|
240
|
+
if isinstance(opt, float):
|
|
241
|
+
opt = Adam(opt)
|
|
242
|
+
losses = {"mse": MSELoss, "bce": BCELoss, "ce": CrossEntropyLoss}
|
|
243
|
+
criterion = losses[loss]() if isinstance(loss, str) else loss
|
|
244
|
+
n = len(X)
|
|
245
|
+
history = []
|
|
246
|
+
for ep in range(epochs):
|
|
247
|
+
idx = rng.permutation(n) if shuffle else np.arange(n)
|
|
248
|
+
total, nb = 0.0, 0
|
|
249
|
+
bs = batch_size or n
|
|
250
|
+
for s in range(0, n, bs):
|
|
251
|
+
bi = idx[s:s + bs]
|
|
252
|
+
xb, yb = X[bi], y[bi]
|
|
253
|
+
# reshape y for mse/bce single-output
|
|
254
|
+
if yb.ndim == 1 and not isinstance(criterion, CrossEntropyLoss):
|
|
255
|
+
yb = yb.reshape(-1, 1)
|
|
256
|
+
pred = self.forward(xb)
|
|
257
|
+
total += criterion.forward(pred, yb) * len(bi)
|
|
258
|
+
grad = criterion.backward()
|
|
259
|
+
for l in reversed(self.layers):
|
|
260
|
+
grad = l.backward(grad)
|
|
261
|
+
opt.step(self.layers)
|
|
262
|
+
nb += len(bi)
|
|
263
|
+
history.append(total / max(nb, 1))
|
|
264
|
+
if verbose and (ep % max(1, epochs // 10) == 0 or ep == epochs - 1):
|
|
265
|
+
print(f"epoch {ep + 1}/{epochs} loss={history[-1]:.6f}")
|
|
266
|
+
return history
|
|
267
|
+
|
|
268
|
+
def save(self, path):
|
|
269
|
+
import pickle
|
|
270
|
+
with open(path, "wb") as f:
|
|
271
|
+
pickle.dump(self, f)
|
|
272
|
+
return path
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def load(path):
|
|
276
|
+
import pickle
|
|
277
|
+
with open(path, "rb") as f:
|
|
278
|
+
return pickle.load(f)
|
mumpy/preprocessing.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""mumpy.preprocessing: sklearn-like transformers in pure numpy.
|
|
2
|
+
|
|
3
|
+
StandardScaler / MinMaxScaler / RobustScaler / MaxAbsScaler
|
|
4
|
+
OneHotEncoder / LabelEncoder / OrdinalEncoder
|
|
5
|
+
SimpleImputer / train_test_split / PolynomialFeatures / Pipeline
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"StandardScaler", "MinMaxScaler", "RobustScaler", "MaxAbsScaler",
|
|
13
|
+
"OneHotEncoder", "LabelEncoder", "SimpleImputer",
|
|
14
|
+
"train_test_split", "shuffle", "PolynomialFeatures", "Pipeline",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StandardScaler:
|
|
19
|
+
def __init__(self, with_mean=True, with_std=True):
|
|
20
|
+
self.with_mean = with_mean
|
|
21
|
+
self.with_std = with_std
|
|
22
|
+
self.mean_ = None
|
|
23
|
+
self.scale_ = None
|
|
24
|
+
|
|
25
|
+
def fit(self, X):
|
|
26
|
+
X = np.asanyarray(X, dtype=float)
|
|
27
|
+
self.mean_ = np.nanmean(X, axis=0) if self.with_mean else np.zeros(X.shape[1])
|
|
28
|
+
self.scale_ = np.nanstd(X, axis=0) if self.with_std else np.ones(X.shape[1])
|
|
29
|
+
self.scale_[self.scale_ == 0] = 1.0
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def transform(self, X):
|
|
33
|
+
X = np.asanyarray(X, dtype=float).copy()
|
|
34
|
+
if self.with_mean:
|
|
35
|
+
X -= self.mean_
|
|
36
|
+
if self.with_std:
|
|
37
|
+
X /= self.scale_
|
|
38
|
+
return X
|
|
39
|
+
|
|
40
|
+
def fit_transform(self, X):
|
|
41
|
+
return self.fit(X).transform(X)
|
|
42
|
+
|
|
43
|
+
def inverse_transform(self, X):
|
|
44
|
+
X = np.asanyarray(X, dtype=float).copy()
|
|
45
|
+
if self.with_std:
|
|
46
|
+
X *= self.scale_
|
|
47
|
+
if self.with_mean:
|
|
48
|
+
X += self.mean_
|
|
49
|
+
return X
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MinMaxScaler:
|
|
53
|
+
def __init__(self, feature_range=(0, 1)):
|
|
54
|
+
self.feature_range = feature_range
|
|
55
|
+
self.min_ = None
|
|
56
|
+
self.scale_ = None
|
|
57
|
+
self.data_min_ = None
|
|
58
|
+
self.data_max_ = None
|
|
59
|
+
|
|
60
|
+
def fit(self, X):
|
|
61
|
+
X = np.asanyarray(X, dtype=float)
|
|
62
|
+
self.data_min_ = np.nanmin(X, axis=0)
|
|
63
|
+
self.data_max_ = np.nanmax(X, axis=0)
|
|
64
|
+
rng = self.data_max_ - self.data_min_
|
|
65
|
+
rng[rng == 0] = 1.0
|
|
66
|
+
lo, hi = self.feature_range
|
|
67
|
+
self.scale_ = (hi - lo) / rng
|
|
68
|
+
self.min_ = lo - self.data_min_ * self.scale_
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def transform(self, X):
|
|
72
|
+
X = np.asanyarray(X, dtype=float).copy()
|
|
73
|
+
return X * self.scale_ + self.min_
|
|
74
|
+
|
|
75
|
+
def fit_transform(self, X):
|
|
76
|
+
return self.fit(X).transform(X)
|
|
77
|
+
|
|
78
|
+
def inverse_transform(self, X):
|
|
79
|
+
X = np.asanyarray(X, dtype=float).copy()
|
|
80
|
+
return (X - self.min_) / self.scale_
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RobustScaler:
|
|
84
|
+
def __init__(self, quantile_range=(25.0, 75.0)):
|
|
85
|
+
self.quantile_range = quantile_range
|
|
86
|
+
self.center_ = None
|
|
87
|
+
self.scale_ = None
|
|
88
|
+
|
|
89
|
+
def fit(self, X):
|
|
90
|
+
X = np.asanyarray(X, dtype=float)
|
|
91
|
+
qmin, qmax = self.quantile_range
|
|
92
|
+
self.center_ = np.nanmedian(X, axis=0)
|
|
93
|
+
q1 = np.nanpercentile(X, qmin, axis=0)
|
|
94
|
+
q3 = np.nanpercentile(X, qmax, axis=0)
|
|
95
|
+
self.scale_ = q3 - q1
|
|
96
|
+
self.scale_[self.scale_ == 0] = 1.0
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def transform(self, X):
|
|
100
|
+
return (np.asanyarray(X, dtype=float) - self.center_) / self.scale_
|
|
101
|
+
|
|
102
|
+
def fit_transform(self, X):
|
|
103
|
+
return self.fit(X).transform(X)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class MaxAbsScaler:
|
|
107
|
+
def fit(self, X):
|
|
108
|
+
X = np.asanyarray(X, dtype=float)
|
|
109
|
+
self.max_abs_ = np.nanmax(np.abs(X), axis=0)
|
|
110
|
+
self.max_abs_[self.max_abs_ == 0] = 1.0
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def transform(self, X):
|
|
114
|
+
return np.asanyarray(X, dtype=float) / self.max_abs_
|
|
115
|
+
|
|
116
|
+
def fit_transform(self, X):
|
|
117
|
+
return self.fit(X).transform(X)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class OneHotEncoder:
|
|
121
|
+
def __init__(self, sparse=False):
|
|
122
|
+
self.sparse = sparse
|
|
123
|
+
self.categories_ = None
|
|
124
|
+
|
|
125
|
+
def fit(self, y):
|
|
126
|
+
y = np.asanyarray(y).ravel()
|
|
127
|
+
self.categories_ = np.unique(y)
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def transform(self, y):
|
|
131
|
+
y = np.asanyarray(y).ravel()
|
|
132
|
+
idx = {v: i for i, v in enumerate(self.categories_.tolist())}
|
|
133
|
+
out = np.zeros((y.size, len(self.categories_)), dtype=float)
|
|
134
|
+
for i, v in enumerate(y.tolist()):
|
|
135
|
+
if v in idx:
|
|
136
|
+
out[i, idx[v]] = 1.0
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
def fit_transform(self, y):
|
|
140
|
+
return self.fit(y).transform(y)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class LabelEncoder:
|
|
144
|
+
def fit(self, y):
|
|
145
|
+
self.classes_ = np.unique(np.asanyarray(y).ravel())
|
|
146
|
+
self._map = {v: i for i, v in enumerate(self.classes_.tolist())}
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def transform(self, y):
|
|
150
|
+
y = np.asanyarray(y).ravel()
|
|
151
|
+
return np.array([self._map[v] for v in y.tolist()])
|
|
152
|
+
|
|
153
|
+
def fit_transform(self, y):
|
|
154
|
+
return self.fit(y).transform(y)
|
|
155
|
+
|
|
156
|
+
def inverse_transform(self, y):
|
|
157
|
+
return np.array([self.classes_[int(i)] for i in np.asanyarray(y).ravel()])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class SimpleImputer:
|
|
161
|
+
def __init__(self, strategy="mean", fill_value=0.0):
|
|
162
|
+
self.strategy = strategy
|
|
163
|
+
self.fill_value = fill_value
|
|
164
|
+
|
|
165
|
+
def fit(self, X):
|
|
166
|
+
X = np.asanyarray(X, dtype=float)
|
|
167
|
+
if self.strategy == "mean":
|
|
168
|
+
self.statistics_ = np.nanmean(X, axis=0)
|
|
169
|
+
elif self.strategy == "median":
|
|
170
|
+
self.statistics_ = np.nanmedian(X, axis=0)
|
|
171
|
+
elif self.strategy == "most_frequent":
|
|
172
|
+
self.statistics_ = np.array([np.unique(c[~np.isnan(c)])[0] if (~np.isnan(c)).any() else self.fill_value
|
|
173
|
+
for c in X.T])
|
|
174
|
+
else:
|
|
175
|
+
self.statistics_ = np.full(X.shape[1], self.fill_value)
|
|
176
|
+
self.statistics_ = np.where(np.isnan(self.statistics_), self.fill_value, self.statistics_)
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def transform(self, X):
|
|
180
|
+
X = np.asanyarray(X, dtype=float).copy()
|
|
181
|
+
idx = np.where(np.isnan(X))
|
|
182
|
+
X[idx] = np.take(self.statistics_, idx[1])
|
|
183
|
+
return X
|
|
184
|
+
|
|
185
|
+
def fit_transform(self, X):
|
|
186
|
+
return self.fit(X).transform(X)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def train_test_split(*arrays, test_size=0.2, random_state=None, shuffle=True):
|
|
190
|
+
rng = np.random.default_rng(random_state)
|
|
191
|
+
n = len(np.asanyarray(arrays[0]))
|
|
192
|
+
idx = np.arange(n)
|
|
193
|
+
if shuffle:
|
|
194
|
+
rng.shuffle(idx)
|
|
195
|
+
n_test = int(n * test_size) if isinstance(test_size, float) else int(test_size)
|
|
196
|
+
te, tr = idx[:n_test], idx[n_test:]
|
|
197
|
+
out = []
|
|
198
|
+
for a in arrays:
|
|
199
|
+
a = np.asanyarray(a)
|
|
200
|
+
out += [a[tr], a[te]]
|
|
201
|
+
return out
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def shuffle(*arrays, random_state=None):
|
|
205
|
+
rng = np.random.default_rng(random_state)
|
|
206
|
+
n = len(np.asanyarray(arrays[0]))
|
|
207
|
+
idx = np.arange(n)
|
|
208
|
+
rng.shuffle(idx)
|
|
209
|
+
return [np.asanyarray(a)[idx] for a in arrays]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class PolynomialFeatures:
|
|
213
|
+
def __init__(self, degree=2, include_bias=True):
|
|
214
|
+
self.degree = degree
|
|
215
|
+
self.include_bias = include_bias
|
|
216
|
+
|
|
217
|
+
def fit(self, X):
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def transform(self, X):
|
|
221
|
+
from itertools import combinations_with_replacement
|
|
222
|
+
X = np.asanyarray(X, dtype=float)
|
|
223
|
+
n, d = X.shape
|
|
224
|
+
feats = [np.ones((n, 1))] if self.include_bias else []
|
|
225
|
+
for deg in range(1, self.degree + 1):
|
|
226
|
+
for combo in combinations_with_replacement(range(d), deg):
|
|
227
|
+
feats.append(np.prod(X[:, combo], axis=1, keepdims=True))
|
|
228
|
+
return np.hstack(feats)
|
|
229
|
+
|
|
230
|
+
def fit_transform(self, X):
|
|
231
|
+
return self.fit(X).transform(X)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class Pipeline:
|
|
235
|
+
def __init__(self, steps):
|
|
236
|
+
self.steps = steps # [(name, transformer/estimator)]
|
|
237
|
+
|
|
238
|
+
def fit(self, X, y=None):
|
|
239
|
+
Xt = X
|
|
240
|
+
for _, s in self.steps[:-1]:
|
|
241
|
+
Xt = s.fit_transform(Xt) if y is None else s.fit(Xt, y).transform(Xt) if hasattr(s, "transform") else s.fit(Xt, y)
|
|
242
|
+
name, est = self.steps[-1]
|
|
243
|
+
if y is None:
|
|
244
|
+
est.fit(Xt)
|
|
245
|
+
else:
|
|
246
|
+
est.fit(Xt, y)
|
|
247
|
+
return self
|
|
248
|
+
|
|
249
|
+
def predict(self, X):
|
|
250
|
+
Xt = X
|
|
251
|
+
for _, s in self.steps[:-1]:
|
|
252
|
+
Xt = s.transform(Xt)
|
|
253
|
+
return self.steps[-1][1].predict(Xt)
|
|
254
|
+
|
|
255
|
+
def transform(self, X):
|
|
256
|
+
Xt = X
|
|
257
|
+
for _, s in self.steps:
|
|
258
|
+
Xt = s.transform(Xt)
|
|
259
|
+
return Xt
|
mumpy/random.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""mumpy.random: numpy.random-compatible API over np.random.Generator (PCG64)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
__all__ = ["default_rng", "rand", "randn", "randint", "random", "choice",
|
|
7
|
+
"shuffle", "permutation", "normal", "uniform", "seed", "Generator",
|
|
8
|
+
"integers", "exponential", "poisson", "binomial", "multivariate_normal",
|
|
9
|
+
"beta", "gamma", "multinomial", "standard_cauchy", "laplace"]
|
|
10
|
+
|
|
11
|
+
_global_rng = np.random.default_rng()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def default_rng(seed=None):
|
|
15
|
+
return np.random.default_rng(seed)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def seed(s=None):
|
|
19
|
+
global _global_rng
|
|
20
|
+
_global_rng = np.random.default_rng(s)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def rand(*args):
|
|
24
|
+
return _global_rng.random(args if args else None)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def random(size=None):
|
|
28
|
+
return _global_rng.random(size)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def randn(*args):
|
|
32
|
+
return _global_rng.standard_normal(args if args else None)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def randint(low, high=None, size=None, dtype=int):
|
|
36
|
+
return _global_rng.integers(low, high, size=size, dtype=dtype, endpoint=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def integers(low, high=None, size=None, dtype=np.int64, endpoint=False):
|
|
40
|
+
return _global_rng.integers(low, high, size=size, dtype=dtype, endpoint=endpoint)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def choice(a, size=None, replace=True, p=None):
|
|
44
|
+
return _global_rng.choice(a, size=size, replace=replace, p=p)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def shuffle(x):
|
|
48
|
+
return _global_rng.shuffle(x)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def permutation(x):
|
|
52
|
+
return _global_rng.permutation(x)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def normal(loc=0.0, scale=1.0, size=None):
|
|
56
|
+
return _global_rng.normal(loc, scale, size)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def uniform(low=0.0, high=1.0, size=None):
|
|
60
|
+
return _global_rng.uniform(low, high, size)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def exponential(scale=1.0, size=None):
|
|
64
|
+
return _global_rng.exponential(scale, size)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def poisson(lam=1.0, size=None):
|
|
68
|
+
return _global_rng.poisson(lam, size)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def binomial(n, p, size=None):
|
|
72
|
+
return _global_rng.binomial(n, p, size)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def multivariate_normal(mean, cov, size=None):
|
|
76
|
+
return _global_rng.multivariate_normal(mean, cov, size)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def beta(a, b, size=None):
|
|
80
|
+
return _global_rng.beta(a, b, size)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def gamma(shape, scale=1.0, size=None):
|
|
84
|
+
return _global_rng.gamma(shape, scale, size)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def multinomial(n, pvals, size=None):
|
|
88
|
+
return _global_rng.multinomial(n, pvals, size)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def standard_cauchy(size=None):
|
|
92
|
+
return _global_rng.standard_cauchy(size)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def laplace(loc=0.0, scale=1.0, size=None):
|
|
96
|
+
return _global_rng.laplace(loc, scale, size)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
Generator = np.random.Generator
|