pcntoolkit 0.32.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.
- pcntoolkit/__init__.py +4 -0
- pcntoolkit/configs.py +9 -0
- pcntoolkit/dataio/__init__.py +1 -0
- pcntoolkit/dataio/fileio.py +608 -0
- pcntoolkit/model/KnuOp.py +48 -0
- pcntoolkit/model/NP.py +88 -0
- pcntoolkit/model/NPR.py +86 -0
- pcntoolkit/model/SHASH.py +509 -0
- pcntoolkit/model/__init__.py +6 -0
- pcntoolkit/model/architecture.py +219 -0
- pcntoolkit/model/bayesreg.py +585 -0
- pcntoolkit/model/core.21290 +0 -0
- pcntoolkit/model/gp.py +489 -0
- pcntoolkit/model/hbr.py +1584 -0
- pcntoolkit/model/rfa.py +245 -0
- pcntoolkit/normative.py +1647 -0
- pcntoolkit/normative_NP.py +336 -0
- pcntoolkit/normative_model/__init__.py +6 -0
- pcntoolkit/normative_model/norm_base.py +62 -0
- pcntoolkit/normative_model/norm_blr.py +303 -0
- pcntoolkit/normative_model/norm_gpr.py +112 -0
- pcntoolkit/normative_model/norm_hbr.py +752 -0
- pcntoolkit/normative_model/norm_np.py +333 -0
- pcntoolkit/normative_model/norm_rfa.py +109 -0
- pcntoolkit/normative_model/norm_utils.py +29 -0
- pcntoolkit/normative_parallel.py +1420 -0
- pcntoolkit/regression_model/blr/warp.py +1 -0
- pcntoolkit/trendsurf.py +315 -0
- pcntoolkit/util/__init__.py +1 -0
- pcntoolkit/util/bspline.py +149 -0
- pcntoolkit/util/hbr_utils.py +242 -0
- pcntoolkit/util/utils.py +1698 -0
- pcntoolkit-0.32.0.dist-info/LICENSE +674 -0
- pcntoolkit-0.32.0.dist-info/METADATA +134 -0
- pcntoolkit-0.32.0.dist-info/RECORD +37 -0
- pcntoolkit-0.32.0.dist-info/WHEEL +4 -0
- pcntoolkit-0.32.0.dist-info/entry_points.txt +5 -0
pcntoolkit/model/rfa.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
from __future__ import division
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GPRRFA:
|
|
9
|
+
"""Random Feature Approximation for Gaussian Process Regression
|
|
10
|
+
|
|
11
|
+
Estimation and prediction of Bayesian linear regression models
|
|
12
|
+
|
|
13
|
+
Basic usage::
|
|
14
|
+
|
|
15
|
+
R = GPRRFA()
|
|
16
|
+
hyp = R.estimate(hyp0, X, y)
|
|
17
|
+
ys,s2 = R.predict(hyp, X, y, Xs)
|
|
18
|
+
|
|
19
|
+
where the variables are
|
|
20
|
+
|
|
21
|
+
:param hyp: vector of hyperparmaters.
|
|
22
|
+
:param X: N x D data array
|
|
23
|
+
:param y: 1D Array of targets (length N)
|
|
24
|
+
:param Xs: Nte x D array of test cases
|
|
25
|
+
:param hyp0: starting estimates for hyperparameter optimisation
|
|
26
|
+
|
|
27
|
+
:returns: * ys - predictive mean
|
|
28
|
+
* s2 - predictive variance
|
|
29
|
+
|
|
30
|
+
The hyperparameters are::
|
|
31
|
+
|
|
32
|
+
hyp = [ log(sn), log(ell), log(sf) ] # hyp is a numpy array
|
|
33
|
+
|
|
34
|
+
where sn^2 is the noise variance, ell are lengthscale parameters and
|
|
35
|
+
sf^2 is the signal variance. This provides an approximation to the
|
|
36
|
+
covariance function::
|
|
37
|
+
|
|
38
|
+
k(x,z) = x'*z + sn2*exp(0.5*(x-z)'*Lambda*(x-z))
|
|
39
|
+
|
|
40
|
+
where Lambda = diag((ell_1^2, ... ell_D^2))
|
|
41
|
+
|
|
42
|
+
Written by A. Marquand
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, hyp=None, X=None, y=None, n_feat=None,
|
|
46
|
+
n_iter=100, tol=1e-3, verbose=False):
|
|
47
|
+
|
|
48
|
+
self.hyp = np.nan
|
|
49
|
+
self.nlZ = np.nan
|
|
50
|
+
self.tol = tol # not used at present
|
|
51
|
+
self.Nf = n_feat
|
|
52
|
+
self.n_iter = n_iter
|
|
53
|
+
self.verbose = verbose
|
|
54
|
+
self._n_restarts = 5
|
|
55
|
+
|
|
56
|
+
if (hyp is not None) and (X is not None) and (y is not None):
|
|
57
|
+
self.post(hyp, X, y)
|
|
58
|
+
|
|
59
|
+
def _numpy2torch(self, X, y=None, hyp=None):
|
|
60
|
+
|
|
61
|
+
if type(X) is torch.Tensor:
|
|
62
|
+
pass
|
|
63
|
+
elif type(X) is np.ndarray:
|
|
64
|
+
X = torch.from_numpy(X)
|
|
65
|
+
else:
|
|
66
|
+
ValueError('Unknown data type (X)')
|
|
67
|
+
X = X.double()
|
|
68
|
+
|
|
69
|
+
if y is not None:
|
|
70
|
+
if type(y) is torch.Tensor:
|
|
71
|
+
pass
|
|
72
|
+
elif type(y) is np.ndarray:
|
|
73
|
+
y = torch.from_numpy(y)
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError('Unknown data type (y)')
|
|
76
|
+
|
|
77
|
+
if len(y.shape) == 1:
|
|
78
|
+
y.resize_(y.shape[0], 1)
|
|
79
|
+
y = y.double()
|
|
80
|
+
|
|
81
|
+
if hyp is not None:
|
|
82
|
+
if type(hyp) is torch.Tensor:
|
|
83
|
+
pass
|
|
84
|
+
else:
|
|
85
|
+
hyp = torch.tensor(hyp, requires_grad=True)
|
|
86
|
+
|
|
87
|
+
return X, y, hyp
|
|
88
|
+
|
|
89
|
+
def get_n_params(self, X):
|
|
90
|
+
|
|
91
|
+
return X.shape[1] + 2
|
|
92
|
+
|
|
93
|
+
def post(self, hyp, X, y):
|
|
94
|
+
""" Generic function to compute posterior distribution.
|
|
95
|
+
|
|
96
|
+
This function will save the posterior mean and precision matrix as
|
|
97
|
+
self.m and self.A and will also update internal parameters (e.g.
|
|
98
|
+
N, D and the prior covariance (Sigma) and precision (iSigma).
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# make sure all variables are the right type
|
|
102
|
+
X, y, hyp = self._numpy2torch(X, y, hyp)
|
|
103
|
+
|
|
104
|
+
self.N, self.Dx = X.shape
|
|
105
|
+
|
|
106
|
+
# ensure the number of features is specified (use 75% as a default)
|
|
107
|
+
if self.Nf is None:
|
|
108
|
+
self.Nf = int(0.75 * self.N)
|
|
109
|
+
|
|
110
|
+
self.Omega = torch.zeros((self.Dx, self.Nf), dtype=torch.double)
|
|
111
|
+
for f in range(self.Nf):
|
|
112
|
+
self.Omega[:, f] = torch.exp(hyp[1:-1]) * \
|
|
113
|
+
torch.randn((self.Dx, 1), dtype=torch.double).squeeze()
|
|
114
|
+
|
|
115
|
+
XO = torch.mm(X, self.Omega)
|
|
116
|
+
self.Phi = torch.exp(hyp[-1])/np.sqrt(self.Nf) * \
|
|
117
|
+
torch.cat((torch.cos(XO), torch.sin(XO)), 1)
|
|
118
|
+
|
|
119
|
+
# concatenate linear weights
|
|
120
|
+
self.Phi = torch.cat((self.Phi, X), 1)
|
|
121
|
+
self.D = self.Phi.shape[1]
|
|
122
|
+
|
|
123
|
+
if self.verbose:
|
|
124
|
+
print("estimating posterior ... | hyp=", hyp)
|
|
125
|
+
|
|
126
|
+
self.A = torch.mm(torch.t(self.Phi), self.Phi) / torch.exp(2*hyp[0]) + \
|
|
127
|
+
torch.eye(self.D, dtype=torch.double)
|
|
128
|
+
self.m = torch.mm(torch.solve(torch.t(self.Phi), self.A)[0], y) / \
|
|
129
|
+
torch.exp(2*hyp[0])
|
|
130
|
+
|
|
131
|
+
# save hyperparameters
|
|
132
|
+
self.hyp = hyp
|
|
133
|
+
|
|
134
|
+
# update optimizer iteration count
|
|
135
|
+
if hasattr(self, '_iterations'):
|
|
136
|
+
self._iterations += 1
|
|
137
|
+
|
|
138
|
+
def loglik(self, hyp, X, y):
|
|
139
|
+
""" Function to compute compute log (marginal) likelihood """
|
|
140
|
+
X, y, hyp = self._numpy2torch(X, y, hyp)
|
|
141
|
+
|
|
142
|
+
# always recompute the posterior
|
|
143
|
+
self.post(hyp, X, y)
|
|
144
|
+
|
|
145
|
+
# logdetA = 2*torch.sum(torch.log(torch.diag(torch.cholesky(self.A))))
|
|
146
|
+
try:
|
|
147
|
+
# compute the log determinants in a numerically stable way
|
|
148
|
+
logdetA = 2 * \
|
|
149
|
+
torch.sum(torch.log(torch.diag(torch.cholesky(self.A))))
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print("Warning: Estimation of posterior distribution failed")
|
|
152
|
+
print(e)
|
|
153
|
+
# nlZ = torch.tensor(1/np.finfo(float).eps)
|
|
154
|
+
nlZ = torch.tensor(np.nan)
|
|
155
|
+
self._optim_failed = True
|
|
156
|
+
return nlZ
|
|
157
|
+
|
|
158
|
+
# compute negative marginal log likelihood
|
|
159
|
+
nlZ = -0.5 * (self.N*torch.log(1/torch.exp(2*hyp[0])) -
|
|
160
|
+
self.N*np.log(2*np.pi) -
|
|
161
|
+
torch.mm(torch.t(y - torch.mm(self.Phi, self.m)),
|
|
162
|
+
(y - torch.mm(self.Phi, self.m))) /
|
|
163
|
+
torch.exp(2*hyp[0]) -
|
|
164
|
+
torch.mm(torch.t(self.m), self.m) - logdetA)
|
|
165
|
+
|
|
166
|
+
if self.verbose:
|
|
167
|
+
print("nlZ= ", nlZ, " | hyp=", hyp)
|
|
168
|
+
|
|
169
|
+
# save marginal likelihood
|
|
170
|
+
self.nlZ = nlZ
|
|
171
|
+
return nlZ
|
|
172
|
+
|
|
173
|
+
def dloglik(self, hyp, X, y):
|
|
174
|
+
""" Function to compute derivatives """
|
|
175
|
+
|
|
176
|
+
print("derivatives not available")
|
|
177
|
+
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
def estimate(self, hyp0, X, y, optimizer='lbfgs'):
|
|
181
|
+
""" Function to estimate the model """
|
|
182
|
+
|
|
183
|
+
if type(hyp0) is torch.Tensor:
|
|
184
|
+
hyp = hyp0
|
|
185
|
+
hyp0.requires_grad_()
|
|
186
|
+
else:
|
|
187
|
+
hyp = torch.tensor(hyp0, requires_grad=True)
|
|
188
|
+
# save the starting values
|
|
189
|
+
self.hyp0 = hyp
|
|
190
|
+
|
|
191
|
+
if optimizer.lower() == 'lbfgs':
|
|
192
|
+
opt = torch.optim.LBFGS([hyp])
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError("Optimizer " + " not implemented")
|
|
195
|
+
self._iterations = 0
|
|
196
|
+
|
|
197
|
+
def closure():
|
|
198
|
+
opt.zero_grad()
|
|
199
|
+
nlZ = self.loglik(hyp, X, y)
|
|
200
|
+
if not torch.isnan(nlZ):
|
|
201
|
+
nlZ.backward()
|
|
202
|
+
return nlZ
|
|
203
|
+
|
|
204
|
+
for r in range(self._n_restarts):
|
|
205
|
+
self._optim_failed = False
|
|
206
|
+
|
|
207
|
+
nlZ = opt.step(closure)
|
|
208
|
+
|
|
209
|
+
if self._optim_failed:
|
|
210
|
+
print("optimization failed. retrying (", r+1, "of",
|
|
211
|
+
self._n_restarts, ")")
|
|
212
|
+
hyp = torch.randn_like(hyp, requires_grad=True)
|
|
213
|
+
self.hyp0 = hyp
|
|
214
|
+
else:
|
|
215
|
+
print("Optimzation complete after", self._iterations,
|
|
216
|
+
"evaluations. Function value =",
|
|
217
|
+
nlZ.detach().numpy().squeeze())
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
return self.hyp.detach().numpy()
|
|
221
|
+
|
|
222
|
+
def predict(self, hyp, X, y, Xs):
|
|
223
|
+
""" Function to make predictions from the model """
|
|
224
|
+
|
|
225
|
+
X, y, hyp = self._numpy2torch(X, y, hyp)
|
|
226
|
+
Xs, *_ = self._numpy2torch(Xs)
|
|
227
|
+
|
|
228
|
+
if (hyp != self.hyp).all() or not (hasattr(self, 'A')):
|
|
229
|
+
self.post(hyp, X, y)
|
|
230
|
+
|
|
231
|
+
# generate prediction tensors
|
|
232
|
+
XsO = torch.mm(Xs, self.Omega)
|
|
233
|
+
Phis = torch.exp(hyp[-1])/np.sqrt(self.Nf) * \
|
|
234
|
+
torch.cat((torch.cos(XsO), torch.sin(XsO)), 1)
|
|
235
|
+
# add linear component
|
|
236
|
+
Phis = torch.cat((Phis, Xs), 1)
|
|
237
|
+
|
|
238
|
+
ys = torch.mm(Phis, self.m)
|
|
239
|
+
|
|
240
|
+
# compute diag(Phis*(Phis'\A)) avoiding computing off-diagonal entries
|
|
241
|
+
s2 = torch.exp(2*hyp[0]) + \
|
|
242
|
+
torch.sum(Phis * torch.t(torch.solve(torch.t(Phis), self.A)[0]), 1)
|
|
243
|
+
|
|
244
|
+
# return output as numpy arrays
|
|
245
|
+
return ys.detach().numpy().squeeze(), s2.detach().numpy().squeeze()
|