def regression_warp(beta, time, q, y, alpha): """ calculates optimal warping for function linear regression :param beta: numpy ndarray of shape (M,N) of M functions with N samples :param time: vector of size N describing the sample points :param q: numpy ndarray of shape (M,N) of M functions with N samples :param y: numpy ndarray of shape (1,N) of M functions with N samples responses :param alpha: numpy scalar :rtype: numpy array :return gamma_new: warping function """ gam_M = uf.optimum_reparam(beta, time, q) qM = uf.warp_q_gamma(time, q, gam_M) y_M = trapz(qM * beta, time) gam_m = uf.optimum_reparam(-1 * beta, time, q) qm = uf.warp_q_gamma(time, q, gam_m) y_m = trapz(qm * beta, time) if y > alpha + y_M: gamma_new = gam_M elif y < alpha + y_m: gamma_new = gam_m else: gamma_new = uf.zero_crossing(y - alpha, q, beta, time, y_M, y_m, gam_M, gam_m) return gamma_new
def predict(self, newdata=None): """ This function performs prediction on regression model on new data if available or current stored data in object Usage: obj.predict() obj.predict(newdata) :param newdata: dict containing new data for prediction (needs the keys below, if None predicts on training data) :type newdata: dict :param f: (M,N) matrix of functions :param time: vector of time points :param y: truth if available :param smooth: smooth data if needed :param sparam: number of times to run filter """ if newdata != None: f = newdata['f'] time = newdata['time'] y = newdata['y'] q = uf.f_to_srsf(f, time, newdata['smooth']) n = f.shape[1] yhat = np.zeros(n) for ii in range(0, n): diff = self.q - q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff)**2, axis=0)**(1. / 2) q_tmp = uf.warp_q_gamma(time, q[:, ii], self.gamma[:, dist.argmin()]) yhat[ii] = self.alpha + trapz(q_tmp * self.beta, time) if y is None: self.SSE = np.nan else: self.SSE = np.sum((y - yhat)**2) self.y_pred = yhat else: n = self.f.shape[1] yhat = np.zeros(n) for ii in range(0, n): diff = self.q - self.q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff)**2, axis=0)**(1. / 2) q_tmp = uf.warp_q_gamma(self.time, self.q[:, ii], self.gamma[:, dist.argmin()]) yhat[ii] = self.alpha + trapz(q_tmp * self.beta, self.time) self.SSE = np.sum((self.y - yhat)**2) self.y_pred = yhat return
def f_dlogl_pw(v_coef, v_basis, d_basis, sigma_curr, q1, q2): vec = uf.f_basistofunction(v_basis["x"], 0, v_coef, v_basis) psi = uf.f_exp1(vec) N = q1.shape[0] obs_domain = np.linspace(0, 1, N) binsize = np.diff(obs_domain) binsize = binsize.mean() gamma = uf.f_phiinv(psi) q2_warp = uf.warp_q_gamma(obs_domain, q2, gamma) q2_warp_grad = np.gradient(q2_warp, binsize) basismat = d_basis["matrix"] g = np.zeros(N) for i in range(0, basismat.shape[1]): ubar = cumtrapz(basismat[:, i], obs_domain, initial=0) integrand = (q1 - q2_warp) * (-2 * q2_warp_grad * ubar - q2_warp * basismat[:, i]) tmp = 1 / sigma_curr * trapz(integrand, obs_domain) g += tmp * basismat[:, i] out, SSEv = f_vpostlogl_pw(vec, q1, q2, sigma_curr, 0) nll = -1 * out g_coef = v_basis["matrix"].T @ g return nll, g_coef, SSEv
def find_C(C, qn, vec, q0, m, mu_psi): qhat, gamhat, a, U, s, mu_g, g, K = jointfPCAd(qn, vec, C, m, mu_psi) (M,N) = qn.shape time = np.linspace(0,1,M-1) d = np.zeros(N) for i in range(0,N): tmp = uf.warp_q_gamma(time, qhat[0:(M-1),i], uf.invertGamma(gamhat[:,i])) d[i] = trapz((tmp-q0[:,i])*(tmp-q0[:,i]), time) out = sum(d*d)/N return out
def find_C(C, qn, vec, q0, m, mu_psi): qhat, gamhat, a, U, s, mu_g = jointfPCAd(qn, vec, C, m, mu_psi) (M,N) = qn.shape time = np.linspace(0,1,M-1) d = np.zeros(N) for i in range(0,N): tmp = uf.warp_q_gamma(time, qhat[0:(M-1),i], uf.invertGamma(gamhat[:,i])) d[i] = trapz((tmp-q0[:,i])*(tmp-q0[:,i]), time) out = sum(d*d)/N return out
def find_C(C, qn, vec, q0, m, mu_psi, parallel, cores): qhat, gamhat, a, U, s, mu_g, g, K = jointfPCAd(qn, vec, C, m, mu_psi, parallel, cores) (M, N) = qn.shape time = np.linspace(0, 1, M - 1) d = np.zeros(N) if parallel: out = Parallel(n_jobs=cores)( delayed(find_C_sub)(time, qhat[0:(M - 1), n], gamhat[:, n], q0[:, n]) for n in range(N)) d = np.array(out) else: for i in range(0, N): tmp = uf.warp_q_gamma(time, qhat[0:(M - 1), i], uf.invertGamma(gamhat[:, i])) d[i] = trapz((tmp - q0[:, i]) * (tmp - q0[:, i]), time) out = sum(d * d) / N return out
def calc_model(self, B=None, lam=0, df=20, max_itr=20, cores=-1, smooth=False): """ This function identifies a regression model with phase-variability using elastic pca :param B: optional matrix describing Basis elements :param lam: regularization parameter (default 0) :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) """ M = self.f.shape[0] N = self.f.shape[1] if M > 500: parallel = True elif N > 100: parallel = True else: parallel = False binsize = np.diff(self.time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(self.time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] self.B = B # second derivative for regularization Bdiff = np.zeros((M, Nb)) for ii in range(0, Nb): Bdiff[:, ii] = np.gradient(np.gradient(B[:, ii], binsize), binsize) self.Bdiff = Bdiff self.q = uf.f_to_srsf(self.f, self.time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 self.SSE = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp( (self.time[-1] - self.time[0]) * gamma[:, ii] + self.time[0], self.time, self.f[:, ii]) qn[:, ii] = uf.warp_q_gamma(self.time, self.q[:, ii], gamma[:, ii]) # OLS using basis Phi = np.ones((N, Nb + 1)) for ii in range(0, N): for jj in range(1, Nb + 1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj - 1], self.time) R = np.zeros((Nb + 1, Nb + 1)) for ii in range(1, Nb + 1): for jj in range(1, Nb + 1): R[ii, jj] = trapz(Bdiff[:, ii - 1] * Bdiff[:, jj - 1], self.time) xx = np.dot(Phi.T, Phi) inv_xx = inv(xx + lam * R) xy = np.dot(Phi.T, self.y) b = np.dot(inv_xx, xy) alpha = b[0] beta = B.dot(b[1:Nb + 1]) beta = beta.reshape(M) # compute the SSE int_X = np.zeros(N) for ii in range(0, N): int_X[ii] = trapz(qn[:, ii] * beta, self.time) self.SSE[itr - 1] = sum((self.y.reshape(N) - alpha - int_X)**2) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)(delayed(regression_warp)( beta, self.time, self.q[:, n], self.y[n], alpha) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = regression_warp(beta, self.time, self.q[:, ii], self.y[ii], alpha) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 # Last Step with centering of gam gamI = uf.SqrtMeanInverse(gamma_new) gamI_dev = np.gradient(gamI, 1 / float(M - 1)) beta = np.interp((self.time[-1] - self.time[0]) * gamI + self.time[0], self.time, beta) * np.sqrt(gamI_dev) for ii in range(0, N): qn[:, ii] = np.interp( (self.time[-1] - self.time[0]) * gamI + self.time[0], self.time, qn[:, ii]) * np.sqrt(gamI_dev) fn[:, ii] = np.interp( (self.time[-1] - self.time[0]) * gamI + self.time[0], self.time, fn[:, ii]) gamma[:, ii] = np.interp( (self.time[-1] - self.time[0]) * gamI + self.time[0], self.time, gamma_new[:, ii]) self.qn = qn self.fn = fn self.gamma = gamma self.alpha = alpha self.beta = beta self.b = b[1:-1] self.SSE = self.SSE[0:itr] return
def align_fPLS(f, g, time, comps=3, showplot=True, smoothdata=False, delta=0.01, max_itr=100): """ This function aligns a collection of functions while performing principal least squares :param f: numpy ndarray of shape (M,N) of N functions with M samples :param g: numpy ndarray of shape (M,N) of N functions with M samples :param time: vector of size M describing the sample points :param comps: number of fPLS components :param showplot: Shows plots of results using matplotlib (default = T) :param smooth_data: Smooth the data using a box filter (default = F) :param delta: gradient step size :param max_itr: maximum number of iterations :type smooth_data: bool :type f: np.ndarray :type g: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return gn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qfn: aligned srvfs - similar structure to fn :return qgn: aligned srvfs - similar structure to fn :return qf0: original srvf - similar structure to fn :return qg0: original srvf - similar structure to fn :return gam: warping functions - similar structure to fn :return wqf: srsf principal weight functions :return wqg: srsf principal weight functions :return wf: srsf principal weight functions :return wg: srsf principal weight functions :return cost: cost function value """ print ("Initializing...") binsize = np.diff(time) binsize = binsize.mean() eps = np.finfo(np.double).eps M = f.shape[0] N = f.shape[1] f0 = f g0 = g if showplot: plot.f_plot(time, f, title="f Original Data") plot.f_plot(time, g, title="g Original Data") # Compute q-function of f and g f, g1, g2 = uf.gradient_spline(time, f, smoothdata) qf = g1 / np.sqrt(abs(g1) + eps) g, g1, g2 = uf.gradient_spline(time, g, smoothdata) qg = g1 / np.sqrt(abs(g1) + eps) print("Calculating fPLS weight functions for %d Warped Functions..." % N) itr = 0 fi = np.zeros((M, N, max_itr + 1)) fi[:, :, itr] = f gi = np.zeros((M, N, max_itr + 1)) gi[:, :, itr] = g qfi = np.zeros((M, N, max_itr + 1)) qfi[:, :, itr] = qf qgi = np.zeros((M, N, max_itr + 1)) qgi[:, :, itr] = qg wqf1, wqg1, alpha, values, costmp = pls_svd(time, qfi[:, :, itr], qgi[:, :, itr], 2, 0) wqf = np.zeros((M, max_itr + 1)) wqf[:, itr] = wqf1[:, 0] wqg = np.zeros((M, max_itr + 1)) wqg[:, itr] = wqg1[:, 0] gam = np.zeros((M, N, max_itr + 1)) tmp = np.tile(np.linspace(0, 1, M), (N, 1)) gam[:, :, itr] = tmp.transpose() wqf_diff = np.zeros(max_itr + 1) cost = np.zeros(max_itr + 1) cost_diff = 1 while itr <= max_itr: # warping gamtmp = np.ascontiguousarray(gam[:, :, 0]) qftmp = np.ascontiguousarray(qfi[:, :, 0]) qgtmp = np.ascontiguousarray(qgi[:, :, 0]) wqftmp = np.ascontiguousarray(wqf[:, itr]) wqgtmp = np.ascontiguousarray(wqg[:, itr]) gam[:, :, itr + 1] = fpls.fpls_warp(time, gamtmp, qftmp, qgtmp, wqftmp, wqgtmp, display=0, delta=delta, tol=1e-6, max_iter=4000) for k in range(0, N): gam_k = gam[:, k, itr + 1] time0 = (time[-1] - time[0]) * gam_k + time[0] fi[:, k, itr + 1] = np.interp(time0, time, fi[:, k, 0]) gi[:, k, itr + 1] = np.interp(time0, time, gi[:, k, 0]) qfi[:, k, itr + 1] = uf.warp_q_gamma(time, qfi[:, k, 0], gam_k) qgi[:, k, itr + 1] = uf.warp_q_gamma(time, qgi[:, k, 0], gam_k) # PLS wqfi, wqgi, alpha, values, costmp = pls_svd(time, qfi[:, :, itr + 1], qgi[:, :, itr + 1], 2, 0) wqf[:, itr + 1] = wqfi[:, 0] wqg[:, itr + 1] = wqgi[:, 0] wqf_diff[itr] = np.sqrt(sum(wqf[:, itr + 1] - wqf[:, itr]) ** 2) rfi = np.zeros(N) rgi = np.zeros(N) for l in range(0, N): rfi[l] = uf.innerprod_q(time, qfi[:, l, itr + 1], wqf[:, itr + 1]) rgi[l] = uf.innerprod_q(time, qgi[:, l, itr + 1], wqg[:, itr + 1]) cost[itr] = np.cov(rfi, rgi)[1, 0] if itr > 1: cost_diff = cost[itr] - cost[itr - 1] print("Iteration: %d - Diff Value: %f - %f" % (itr + 1, wqf_diff[itr], cost[itr])) if wqf_diff[itr] < 1e-1 or abs(cost_diff) < 1e-3: break itr += 1 cost = cost[0:(itr + 1)] # Aligned data & stats fn = fi[:, :, itr + 1] gn = gi[:, :, itr + 1] qfn = qfi[:, :, itr + 1] qf0 = qfi[:, :, 0] qgn = qgi[:, :, itr + 1] qg0 = qgi[:, :, 0] wqfn, wqgn, alpha, values, costmp = pls_svd(time, qfn, qgn, comps, 0) wf = np.zeros((M, comps)) wg = np.zeros((M, comps)) for ii in range(0, comps): wf[:, ii] = cumtrapz(wqfn[:, ii] * np.abs(wqfn[:, ii]), time, initial=0) wg[:, ii] = cumtrapz(wqgn[:, ii] * np.abs(wqgn[:, ii]), time, initial=0) gam_f = gam[:, :, itr + 1] if showplot: # Align Plots fig, ax = plot.f_plot(np.arange(0, M) / float(M - 1), gam_f, title="Warping Functions") ax.set_aspect('equal') plot.f_plot(time, fn, title="fn Warped Data") plot.f_plot(time, gn, title="gn Warped Data") plot.f_plot(time, wf, title="wf") plot.f_plot(time, wg, title="wg") plt.show() align_fPLSresults = collections.namedtuple('align_fPLS', ['wf', 'wg', 'fn', 'gn', 'qfn', 'qgn', 'qf0', 'qg0', 'wqf', 'wqg', 'gam', 'values', 'cost']) out = align_fPLSresults(wf, wg, fn, gn, qfn, qgn, qf0, qg0, wqfn, wqgn, gam_f, values, cost) return out
def predict(self, newdata=None): """ This function performs prediction on regression model on new data if available or current stored data in object Usage: obj.predict() obj.predict(newdata) :param newdata: dict containing new data for prediction (needs the keys below, if None predicts on training data) :type newdata: dict :param f: (M,N) matrix of functions :param time: vector of time points :param y: truth if available :param smooth: smooth data if needed :param sparam: number of times to run filter """ if newdata != None: f = newdata['f'] time = newdata['time'] y = newdata['y'] q = uf.f_to_srsf(f, time, newdata['smooth']) n = f.shape[1] m = self.n_classes yhat = np.zeros((n, m)) for ii in range(0, n): diff = self.q - q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff)**2, axis=0)**(1. / 2) q_tmp = uf.warp_q_gamma(time, q[:, ii], self.gamma[:, dist.argmin()]) for jj in range(0, m): yhat[ii, jj] = self.alpha[jj] + trapz( q_tmp * self.beta[:, jj], time) if y is None: yhat = phi(yhat.ravel()) yhat = yhat.reshape(n, m) y_labels = yhat.argmax(axis=1) + 1 self.PC = None else: yhat = phi(yhat.ravel()) yhat = yhat.reshape(n, m) y_labels = yhat.argmax(axis=1) + 1 PC = np.zeros(m) cls_set = np.arange(1, m + 1) for ii in range(0, m): cls_sub = np.delete(cls_set, ii) TP = sum(y[y_labels == (ii + 1)] == (ii + 1)) FP = sum(y[np.in1d(y_labels, cls_sub)] == (ii + 1)) TN = sum(y[np.in1d(y_labels, cls_sub)] == y_labels[np.in1d( y_labels, cls_sub)]) FN = sum(np.in1d(y[y_labels == (ii + 1)], cls_sub)) PC[ii] = (TP + TN) / float(TP + FP + FN + TN) self.PC = sum(y == y_labels) / float(y_labels.size) self.y_pred = yhat self.y_labels = y_labels else: n = self.f.shape[1] m = self.n_classes yhat = np.zeros((n, m)) for ii in range(0, n): diff = self.q - self.q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff)**2, axis=0)**(1. / 2) q_tmp = uf.warp_q_gamma(self.time, self.q[:, ii], self.gamma[:, dist.argmin()]) for jj in range(0, m): yhat[ii, jj] = self.alpha[jj] + trapz( q_tmp * self.beta[:, jj], self.time) yhat = phi(yhat.ravel()) yhat = yhat.reshape(n, m) y_labels = yhat.argmax(axis=1) + 1 PC = np.zeros(m) cls_set = np.arange(1, m + 1) for ii in range(0, m): cls_sub = np.delete(cls_set, ii) TP = sum(self.y[y_labels == (ii + 1)] == (ii + 1)) FP = sum(self.y[np.in1d(y_labels, cls_sub)] == (ii + 1)) TN = sum(self.y[np.in1d(y_labels, cls_sub)] == y_labels[ np.in1d(y_labels, cls_sub)]) FN = sum(np.in1d(self.y[y_labels == (ii + 1)], cls_sub)) PC[ii] = (TP + TN) / float(TP + FP + FN + TN) self.PC = sum(self.y == y_labels) / float(y_labels.size) self.y_pred = yhat self.y_labels = y_labels return
def jointfPCA(fn, time, qn, q0, gam, no=2, showplot=True): """ This function calculates joint functional principal component analysis on aligned data :param fn: numpy ndarray of shape (M,N) of N aligned functions with M samples :param time: vector of size N describing the sample points :param qn: numpy ndarray of shape (M,N) of N aligned SRSF with M samples :param no: number of components to extract (default = 2) :param showplot: Shows plots of results using matplotlib (default = T) :type showplot: bool :type no: int :rtype: tuple of numpy ndarray :return q_pca: srsf principal directions :return f_pca: functional principal directions :return latent: latent values :return coef: coefficients :return U: eigenvectors """ coef = np.arange(-1., 2.) Nstd = coef.shape[0] # set up for fPCA in q-space mq_new = qn.mean(axis=1) M = time.shape[0] mididx = int(np.round(M / 2)) m_new = np.sign(fn[mididx, :]) * np.sqrt(np.abs(fn[mididx, :])) mqn = np.append(mq_new, m_new.mean()) qn2 = np.vstack((qn, m_new)) # calculate vector space of warping functions mu_psi, gam_mu, psi, vec = uf.SqrtMean(gam) # joint fPCA C = fminbound(find_C,0,1e4,(qn2,vec,q0,no,mu_psi)) qhat, gamhat, a, U, s, mu_g = jointfPCAd(qn2, vec, C, no, mu_psi) # geodesic paths q_pca = np.ndarray(shape=(M, Nstd, no), dtype=float) f_pca = np.ndarray(shape=(M, Nstd, no), dtype=float) for k in range(0, no): for l in range(0, Nstd): qhat = mqn + dot(U[0:(M+1),k],coef[l]*np.sqrt(s[k])) vechat = dot(U[(M+1):,k],(coef[l]*np.sqrt(s[k]))/C) psihat = geo.exp_map(mu_psi,vechat) gamhat = cumtrapz(psihat*psihat,np.linspace(0,1,M),initial=0) gamhat = (gamhat - gamhat.min()) / (gamhat.max() - gamhat.min()) if (sum(vechat)==0): gamhat = np.linspace(0,1,M) fhat = uf.cumtrapzmid(time, qhat[0:M]*np.fabs(qhat[0:M]), np.sign(qhat[M])*(qhat[M]*qhat[M]), mididx) f_pca[:,l,k] = uf.warp_f_gamma(np.linspace(0,1,M), fhat, gamhat) q_pca[:,l,k] = uf.warp_q_gamma(np.linspace(0,1,M), qhat[0:M], gamhat) jfpca_results = collections.namedtuple('jfpca', ['q_pca', 'f_pca', 'latent', 'coef', 'U']) jfpca = jfpca_results(q_pca, f_pca, s, a, U) if showplot: CBcdict = { 'Bl': (0, 0, 0), 'Or': (.9, .6, 0), 'SB': (.35, .7, .9), 'bG': (0, .6, .5), 'Ye': (.95, .9, .25), 'Bu': (0, .45, .7), 'Ve': (.8, .4, 0), 'rP': (.8, .6, .7), } cl = sorted(CBcdict.keys()) fig, ax = plt.subplots(2, no) for k in range(0, no): axt = ax[0, k] for l in range(0, Nstd): axt.plot(time, q_pca[0:M, l, k], color=CBcdict[cl[l]]) axt.set_title('q domain: PD %d' % (k + 1)) axt = ax[1, k] for l in range(0, Nstd): axt.plot(time, f_pca[:, l, k], color=CBcdict[cl[l]]) axt.set_title('f domain: PD %d' % (k + 1)) fig.set_tight_layout(True) cumm_coef = 100 * np.cumsum(s) / sum(s) idx = np.arange(0, s.shape[0]) + 1 plot.f_plot(idx, cumm_coef, "Coefficient Cumulative Percentage") plt.xlabel("Percentage") plt.ylabel("Index") plt.show() return jfpca
def elastic_mlogistic(f, y, time, B=None, df=20, max_itr=20, cores=-1, delta=.01, parallel=True, smooth=False): """ This function identifies a multinomial logistic regression model with phase-variablity using elastic methods :param f: numpy ndarray of shape (M,N) of N functions with M samples :param y: numpy array of labels {1,2,...,m} for m classes :param time: vector of size M describing the sample points :param B: optional matrix describing Basis elements :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) :type f: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return Loss: logistic loss """ M = f.shape[0] N = f.shape[1] # Code labels m = y.max() Y = np.zeros((N, m), dtype=int) for ii in range(0, N): Y[ii, y[ii]-1] = 1 binsize = np.diff(time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] q = uf.f_to_srsf(f, time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 LL = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp((time[-1] - time[0]) * gamma[:, ii] + time[0], time, f[:, ii]) qn[:, ii] = uf.warp_q_gamma(time, q[:, ii], gamma[:, ii]) Phi = np.ones((N, Nb+1)) for ii in range(0, N): for jj in range(1, Nb+1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj-1], time) # Find alpha and beta using l_bfgs b0 = np.zeros(m * (Nb+1)) out = fmin_l_bfgs_b(mlogit_loss, b0, fprime=mlogit_gradient, args=(Phi, Y), pgtol=1e-10, maxiter=200, maxfun=250, factr=1e-30) b = out[0] B0 = b.reshape(Nb+1, m) alpha = B0[0, :] beta = np.zeros((M, m)) for i in range(0, m): beta[:, i] = B.dot(B0[1:Nb+1, i]) # compute the logistic loss LL[itr - 1] = mlogit_loss(b, Phi, Y) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)(delayed(mlogit_warp_grad)(alpha, beta, time, q[:, n], Y[n, :], delta=delta) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = mlogit_warp_grad(alpha, beta, time, q[:, ii], Y[ii, :], delta=delta) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 # Last Step with centering of gam gamma = gamma_new # gamI = uf.SqrtMeanInverse(gamma) # gamI_dev = np.gradient(gamI, 1 / float(M - 1)) # beta = np.interp((time[-1] - time[0]) * gamI + time[0], time, # beta) * np.sqrt(gamI_dev) # for ii in range(0, N): # qn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, qn[:, ii]) * np.sqrt(gamI_dev) # fn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, fn[:, ii]) # gamma[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, gamma[:, ii]) model = collections.namedtuple('model', ['alpha', 'beta', 'fn', 'qn', 'gamma', 'q', 'B', 'b', 'Loss', 'n_classes', 'type']) out = model(alpha, beta, fn, qn, gamma, q, B, b[1:-1], LL[0:itr], m, 'mlogistic') return out
def elastic_prediction(f, time, model, y=None, smooth=False): """ This function performs prediction from an elastic regression model with phase-variablity :param f: numpy ndarray of shape (M,N) of N functions with M samples :param time: vector of size M describing the sample points :param model: indentified model from elastic_regression :param y: truth, optional used to calculate SSE :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return SSE: sum of squared error """ q = uf.f_to_srsf(f, time, smooth) n = q.shape[1] if model.type == 'linear' or model.type == 'logistic': y_pred = np.zeros(n) elif model.type == 'mlogistic': m = model.n_classes y_pred = np.zeros((n, m)) for ii in range(0, n): diff = model.q - q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff) ** 2, axis=0) ** (1. / 2) q_tmp = uf.warp_q_gamma(time, q[:, ii], model.gamma[:, dist.argmin()]) if model.type == 'linear': y_pred[ii] = model.alpha + trapz(q_tmp * model.beta, time) elif model.type == 'logistic': y_pred[ii] = model.alpha + trapz(q_tmp * model.beta, time) elif model.type == 'mlogistic': for jj in range(0, m): y_pred[ii, jj] = model.alpha[jj] + trapz(q_tmp * model.beta[:, jj], time) if y is None: if model.type == 'linear': SSE = None elif model.type == 'logistic': y_pred = phi(y_pred) y_labels = np.ones(n) y_labels[y_pred < 0.5] = -1 PC = None elif model.type == 'mlogistic': y_pred = phi(y_pred.ravel()) y_pred = y_pred.reshape(n, m) y_labels = y_pred.argmax(axis=1)+1 PC = None else: if model.type == 'linear': SSE = sum((y - y_pred) ** 2) elif model.type == 'logistic': y_pred = phi(y_pred) y_labels = np.ones(n) y_labels[y_pred < 0.5] = -1 TP = sum(y[y_labels == 1] == 1) FP = sum(y[y_labels == -1] == 1) TN = sum(y[y_labels == -1] == -1) FN = sum(y[y_labels == 1] == -1) PC = (TP+TN)/float(TP+FP+FN+TN) elif model.type == 'mlogistic': y_pred = phi(y_pred.ravel()) y_pred = y_pred.reshape(n, m) y_labels = y_pred.argmax(axis=1)+1 PC = np.zeros(m) cls_set = np.arange(1, m+1) for ii in range(0, m): cls_sub = np.delete(cls_set, ii) TP = sum(y[y_labels == (ii+1)] == (ii+1)) FP = sum(y[np.in1d(y_labels, cls_sub)] == (ii+1)) TN = sum(y[np.in1d(y_labels, cls_sub)] == y_labels[np.in1d(y_labels, cls_sub)]) FN = sum(np.in1d(y[y_labels == (ii+1)], cls_sub)) PC[ii] = (TP+TN)/float(TP+FP+FN+TN) PC = sum(y == y_labels) / float(y_labels.size) if model.type == 'linear': prediction = collections.namedtuple('prediction', ['y_pred', 'SSE']) out = prediction(y_pred, SSE) elif model.type == 'logistic': prediction = collections.namedtuple('prediction', ['y_prob', 'y_labels', 'PC']) out = prediction(y_pred, y_labels, PC) elif model.type == 'mlogistic': prediction = collections.namedtuple('prediction', ['y_prob', 'y_labels', 'PC']) out = prediction(y_pred, y_labels, PC) return out
def elastic_regression(f, y, time, B=None, lam=0, df=20, max_itr=20, cores=-1, smooth=False): """ This function identifies a regression model with phase-variablity using elastic methods :param f: numpy ndarray of shape (M,N) of N functions with M samples :param y: numpy array of N responses :param time: vector of size M describing the sample points :param B: optional matrix describing Basis elements :param lam: regularization parameter (default 0) :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) :type f: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of M functions with N samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return SSE: sum of squared error """ M = f.shape[0] N = f.shape[1] if M > 500: parallel = True elif N > 100: parallel = True else: parallel = False binsize = np.diff(time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] # second derivative for regularization Bdiff = np.zeros((M, Nb)) for ii in range(0, Nb): Bdiff[:, ii] = np.gradient(np.gradient(B[:, ii], binsize), binsize) q = uf.f_to_srsf(f, time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 SSE = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp( (time[-1] - time[0]) * gamma[:, ii] + time[0], time, f[:, ii]) qn[:, ii] = uf.warp_q_gamma(time, q[:, ii], gamma[:, ii]) # OLS using basis Phi = np.ones((N, Nb + 1)) for ii in range(0, N): for jj in range(1, Nb + 1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj - 1], time) R = np.zeros((Nb + 1, Nb + 1)) for ii in range(1, Nb + 1): for jj in range(1, Nb + 1): R[ii, jj] = trapz(Bdiff[:, ii - 1] * Bdiff[:, jj - 1], time) xx = dot(Phi.T, Phi) inv_xx = inv(xx + lam * R) xy = dot(Phi.T, y) b = dot(inv_xx, xy) alpha = b[0] beta = B.dot(b[1:Nb + 1]) beta = beta.reshape(M) # compute the SSE int_X = np.zeros(N) for ii in range(0, N): int_X[ii] = trapz(qn[:, ii] * beta, time) SSE[itr - 1] = sum((y.reshape(N) - alpha - int_X)**2) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)( delayed(regression_warp)(beta, time, q[:, n], y[n], alpha) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = regression_warp(beta, time, q[:, ii], y[ii], alpha) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 # Last Step with centering of gam gamI = uf.SqrtMeanInverse(gamma_new) gamI_dev = np.gradient(gamI, 1 / float(M - 1)) beta = np.interp( (time[-1] - time[0]) * gamI + time[0], time, beta) * np.sqrt(gamI_dev) for ii in range(0, N): qn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, qn[:, ii]) * np.sqrt(gamI_dev) fn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, fn[:, ii]) gamma[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, gamma_new[:, ii]) model = collections.namedtuple( 'model', ['alpha', 'beta', 'fn', 'qn', 'gamma', 'q', 'B', 'b', 'SSE', 'type']) out = model(alpha, beta, fn, qn, gamma, q, B, b[1:-1], SSE[0:itr], 'linear') return out
def elastic_regression(f, y, time, B=None, lam=0, df=20, max_itr=20, cores=-1, smooth=False): """ This function identifies a regression model with phase-variablity using elastic methods :param f: numpy ndarray of shape (M,N) of N functions with M samples :param y: numpy array of N responses :param time: vector of size M describing the sample points :param B: optional matrix describing Basis elements :param lam: regularization parameter (default 0) :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) :type f: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of M functions with N samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return SSE: sum of squared error """ M = f.shape[0] N = f.shape[1] if M > 500: parallel = True elif N > 100: parallel = True else: parallel = False binsize = np.diff(time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] # second derivative for regularization Bdiff = np.zeros((M, Nb)) for ii in range(0, Nb): Bdiff[:, ii] = np.gradient(np.gradient(B[:, ii], binsize), binsize) q = uf.f_to_srsf(f, time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 SSE = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp((time[-1] - time[0]) * gamma[:, ii] + time[0], time, f[:, ii]) qn[:, ii] = uf.warp_q_gamma(time, q[:, ii], gamma[:, ii]) # OLS using basis Phi = np.ones((N, Nb+1)) for ii in range(0, N): for jj in range(1, Nb+1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj-1], time) R = np.zeros((Nb+1, Nb+1)) for ii in range(1, Nb+1): for jj in range(1, Nb+1): R[ii, jj] = trapz(Bdiff[:, ii-1] * Bdiff[:, jj-1], time) xx = dot(Phi.T, Phi) inv_xx = inv(xx + lam * R) xy = dot(Phi.T, y) b = dot(inv_xx, xy) alpha = b[0] beta = B.dot(b[1:Nb+1]) beta = beta.reshape(M) # compute the SSE int_X = np.zeros(N) for ii in range(0, N): int_X[ii] = trapz(qn[:, ii] * beta, time) SSE[itr - 1] = sum((y.reshape(N) - alpha - int_X) ** 2) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)(delayed(regression_warp)(beta, time, q[:, n], y[n], alpha) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = regression_warp(beta, time, q[:, ii], y[ii], alpha) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 # Last Step with centering of gam gamI = uf.SqrtMeanInverse(gamma_new) gamI_dev = np.gradient(gamI, 1 / float(M - 1)) beta = np.interp((time[-1] - time[0]) * gamI + time[0], time, beta) * np.sqrt(gamI_dev) for ii in range(0, N): qn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, qn[:, ii]) * np.sqrt(gamI_dev) fn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, fn[:, ii]) gamma[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], time, gamma_new[:, ii]) model = collections.namedtuple('model', ['alpha', 'beta', 'fn', 'qn', 'gamma', 'q', 'B', 'b', 'SSE', 'type']) out = model(alpha, beta, fn, qn, gamma, q, B, b[1:-1], SSE[0:itr], 'linear') return out
def calc_model(self, B=None, lam=0, df=20, max_itr=20, delta=.01, cores=-1, smooth=False): """ This function identifies a regression model with phase-variability using elastic pca :param B: optional matrix describing Basis elements :param lam: regularization parameter (default 0) :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) """ M = self.f.shape[0] N = self.f.shape[1] m = self.y.max() if M > 500: parallel = True elif N > 100: parallel = True else: parallel = False binsize = np.diff(self.time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(self.time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] self.B = B self.q = uf.f_to_srsf(self.f, self.time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 self.LL = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp( (self.time[-1] - self.time[0]) * gamma[:, ii] + self.time[0], self.time, self.f[:, ii]) qn[:, ii] = uf.warp_q_gamma(self.time, self.q[:, ii], gamma[:, ii]) Phi = np.ones((N, Nb + 1)) for ii in range(0, N): for jj in range(1, Nb + 1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj - 1], self.time) # Find alpha and beta using l_bfgs b0 = np.zeros(m * (Nb + 1)) out = fmin_l_bfgs_b(mlogit_loss, b0, fprime=mlogit_gradient, args=(Phi, self.Y), pgtol=1e-10, maxiter=200, maxfun=250, factr=1e-30) b = out[0] B0 = b.reshape(Nb + 1, m) alpha = B0[0, :] beta = np.zeros((M, m)) for i in range(0, m): beta[:, i] = B.dot(B0[1:Nb + 1, i]) # compute the logistic loss self.LL[itr - 1] = mlogit_loss(b, Phi, self.Y) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)( delayed(mlogit_warp_grad)(alpha, beta, self.time, self.q[:, n], self.Y[n, :], delta=delta) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = mlogit_warp_grad(alpha, beta, self.time, self.q[:, ii], self.Y[ii, :], delta=delta) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 self.qn = qn self.fn = fn self.gamma = gamma self.alpha = alpha self.beta = beta self.b = b[1:-1] self.n_classes = m self.LL = self.LL[0:itr] return
def align_fPLS(f, g, time, comps=3, showplot=True, smoothdata=False, delta=0.01, max_itr=100): """ This function aligns a collection of functions while performing principal least squares :param f: numpy ndarray of shape (M,N) of N functions with M samples :param g: numpy ndarray of shape (M,N) of N functions with M samples :param time: vector of size M describing the sample points :param comps: number of fPLS components :param showplot: Shows plots of results using matplotlib (default = T) :param smooth_data: Smooth the data using a box filter (default = F) :param delta: gradient step size :param max_itr: maximum number of iterations :type smooth_data: bool :type f: np.ndarray :type g: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return gn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qfn: aligned srvfs - similar structure to fn :return qgn: aligned srvfs - similar structure to fn :return qf0: original srvf - similar structure to fn :return qg0: original srvf - similar structure to fn :return gam: warping functions - similar structure to fn :return wqf: srsf principal weight functions :return wqg: srsf principal weight functions :return wf: srsf principal weight functions :return wg: srsf principal weight functions :return cost: cost function value """ print("Initializing...") binsize = np.diff(time) binsize = binsize.mean() eps = np.finfo(np.double).eps M = f.shape[0] N = f.shape[1] f0 = f g0 = g if showplot: plot.f_plot(time, f, title="f Original Data") plot.f_plot(time, g, title="g Original Data") # Compute q-function of f and g f, g1, g2 = uf.gradient_spline(time, f, smoothdata) qf = g1 / np.sqrt(abs(g1) + eps) g, g1, g2 = uf.gradient_spline(time, g, smoothdata) qg = g1 / np.sqrt(abs(g1) + eps) print("Calculating fPLS weight functions for %d Warped Functions..." % N) itr = 0 fi = np.zeros((M, N, max_itr + 1)) fi[:, :, itr] = f gi = np.zeros((M, N, max_itr + 1)) gi[:, :, itr] = g qfi = np.zeros((M, N, max_itr + 1)) qfi[:, :, itr] = qf qgi = np.zeros((M, N, max_itr + 1)) qgi[:, :, itr] = qg wqf1, wqg1, alpha, values, costmp = pls_svd(time, qfi[:, :, itr], qgi[:, :, itr], 2, 0) wqf = np.zeros((M, max_itr + 1)) wqf[:, itr] = wqf1[:, 0] wqg = np.zeros((M, max_itr + 1)) wqg[:, itr] = wqg1[:, 0] gam = np.zeros((M, N, max_itr + 1)) tmp = np.tile(np.linspace(0, 1, M), (N, 1)) gam[:, :, itr] = tmp.transpose() wqf_diff = np.zeros(max_itr + 1) cost = np.zeros(max_itr + 1) cost_diff = 1 while itr <= max_itr: # warping gamtmp = np.ascontiguousarray(gam[:, :, 0]) qftmp = np.ascontiguousarray(qfi[:, :, 0]) qgtmp = np.ascontiguousarray(qgi[:, :, 0]) wqftmp = np.ascontiguousarray(wqf[:, itr]) wqgtmp = np.ascontiguousarray(wqg[:, itr]) gam[:, :, itr + 1] = fpls.fpls_warp(time, gamtmp, qftmp, qgtmp, wqftmp, wqgtmp, display=0, delta=delta, tol=1e-6, max_iter=4000) for k in range(0, N): gam_k = gam[:, k, itr + 1] time0 = (time[-1] - time[0]) * gam_k + time[0] fi[:, k, itr + 1] = np.interp(time0, time, fi[:, k, 0]) gi[:, k, itr + 1] = np.interp(time0, time, gi[:, k, 0]) qfi[:, k, itr + 1] = uf.warp_q_gamma(time, qfi[:, k, 0], gam_k) qgi[:, k, itr + 1] = uf.warp_q_gamma(time, qgi[:, k, 0], gam_k) # PLS wqfi, wqgi, alpha, values, costmp = pls_svd(time, qfi[:, :, itr + 1], qgi[:, :, itr + 1], 2, 0) wqf[:, itr + 1] = wqfi[:, 0] wqg[:, itr + 1] = wqgi[:, 0] wqf_diff[itr] = np.sqrt(sum(wqf[:, itr + 1] - wqf[:, itr])**2) rfi = np.zeros(N) rgi = np.zeros(N) for l in range(0, N): rfi[l] = uf.innerprod_q(time, qfi[:, l, itr + 1], wqf[:, itr + 1]) rgi[l] = uf.innerprod_q(time, qgi[:, l, itr + 1], wqg[:, itr + 1]) cost[itr] = np.cov(rfi, rgi)[1, 0] if itr > 1: cost_diff = cost[itr] - cost[itr - 1] print("Iteration: %d - Diff Value: %f - %f" % (itr + 1, wqf_diff[itr], cost[itr])) if wqf_diff[itr] < 1e-1 or abs(cost_diff) < 1e-3: break itr += 1 cost = cost[0:(itr + 1)] # Aligned data & stats fn = fi[:, :, itr + 1] gn = gi[:, :, itr + 1] qfn = qfi[:, :, itr + 1] qf0 = qfi[:, :, 0] qgn = qgi[:, :, itr + 1] qg0 = qgi[:, :, 0] wqfn, wqgn, alpha, values, costmp = pls_svd(time, qfn, qgn, comps, 0) wf = np.zeros((M, comps)) wg = np.zeros((M, comps)) for ii in range(0, comps): wf[:, ii] = cumtrapz(wqfn[:, ii] * np.abs(wqfn[:, ii]), time, initial=0) wg[:, ii] = cumtrapz(wqgn[:, ii] * np.abs(wqgn[:, ii]), time, initial=0) gam_f = gam[:, :, itr + 1] if showplot: # Align Plots fig, ax = plot.f_plot(np.arange(0, M) / float(M - 1), gam_f, title="Warping Functions") ax.set_aspect('equal') plot.f_plot(time, fn, title="fn Warped Data") plot.f_plot(time, gn, title="gn Warped Data") plot.f_plot(time, wf, title="wf") plot.f_plot(time, wg, title="wg") plt.show() align_fPLSresults = collections.namedtuple('align_fPLS', [ 'wf', 'wg', 'fn', 'gn', 'qfn', 'qgn', 'qf0', 'qg0', 'wqf', 'wqg', 'gam', 'values', 'cost' ]) out = align_fPLSresults(wf, wg, fn, gn, qfn, qgn, qf0, qg0, wqfn, wqgn, gam_f, values, cost) return out
def calc_fpca(self, no=3, stds=np.arange(-1., 2.), id=None, parallel=False, cores=-1): """ This function calculates joint functional principal component analysis on aligned data :param no: number of components to extract (default = 3) :param id: point to use for f(0) (default = midpoint) :param stds: number of standard deviations along gedoesic to compute (default = -1,0,1) :param parallel: run in parallel (default = F) :param cores: number of cores for parallel (default = -1 (all)) :type no: int :type id: int :type parallel: bool :type cores: int :rtype: fdajpca object of numpy ndarray :return q_pca: srsf principal directions :return f_pca: functional principal directions :return latent: latent values :return coef: coefficients :return U: eigenvectors """ fn = self.warp_data.fn time = self.warp_data.time qn = self.warp_data.qn q0 = self.warp_data.q0 gam = self.warp_data.gam M = time.shape[0] if id is None: mididx = int(np.round(M / 2)) else: mididx = id Nstd = stds.shape[0] # set up for fPCA in q-space mq_new = qn.mean(axis=1) m_new = np.sign(fn[mididx, :]) * np.sqrt(np.abs(fn[mididx, :])) mqn = np.append(mq_new, m_new.mean()) qn2 = np.vstack((qn, m_new)) # calculate vector space of warping functions mu_psi, gam_mu, psi, vec = uf.SqrtMean(gam, parallel, cores) # joint fPCA C = fminbound(find_C, 0, 1e4, (qn2, vec, q0, no, mu_psi, parallel, cores)) qhat, gamhat, a, U, s, mu_g, g, cov = jointfPCAd( qn2, vec, C, no, mu_psi, parallel, cores) # geodesic paths q_pca = np.ndarray(shape=(M, Nstd, no), dtype=float) f_pca = np.ndarray(shape=(M, Nstd, no), dtype=float) for k in range(0, no): for l in range(0, Nstd): qhat = mqn + np.dot(U[0:(M + 1), k], stds[l] * np.sqrt(s[k])) vechat = np.dot(U[(M + 1):, k], (stds[l] * np.sqrt(s[k])) / C) psihat = geo.exp_map(mu_psi, vechat) gamhat = cumtrapz(psihat * psihat, np.linspace(0, 1, M), initial=0) gamhat = (gamhat - gamhat.min()) / (gamhat.max() - gamhat.min()) if (sum(vechat) == 0): gamhat = np.linspace(0, 1, M) fhat = uf.cumtrapzmid(time, qhat[0:M] * np.fabs(qhat[0:M]), np.sign(qhat[M]) * (qhat[M] * qhat[M]), mididx) f_pca[:, l, k] = uf.warp_f_gamma(np.linspace(0, 1, M), fhat, gamhat) q_pca[:, l, k] = uf.warp_q_gamma(np.linspace(0, 1, M), qhat[0:M], gamhat) self.q_pca = q_pca self.f_pca = f_pca self.latent = s[0:no] self.coef = a self.U = U[:, 0:no] self.mu_psi = mu_psi self.mu_g = mu_g self.id = mididx self.C = C self.time = time self.g = g self.cov = cov self.no = no self.stds = stds return
def elastic_mlogistic(f, y, time, B=None, df=20, max_itr=20, cores=-1, delta=.01, parallel=True, smooth=False): """ This function identifies a multinomial logistic regression model with phase-variablity using elastic methods :param f: numpy ndarray of shape (M,N) of N functions with M samples :param y: numpy array of labels {1,2,...,m} for m classes :param time: vector of size M describing the sample points :param B: optional matrix describing Basis elements :param df: number of degrees of freedom B-spline (default 20) :param max_itr: maximum number of iterations (default 20) :param cores: number of cores for parallel processing (default all) :type f: np.ndarray :type time: np.ndarray :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return Loss: logistic loss """ M = f.shape[0] N = f.shape[1] # Code labels m = y.max() Y = np.zeros((N, m), dtype=int) for ii in range(0, N): Y[ii, y[ii] - 1] = 1 binsize = np.diff(time) binsize = binsize.mean() # Create B-Spline Basis if none provided if B is None: B = bs(time, df=df, degree=4, include_intercept=True) Nb = B.shape[1] q = uf.f_to_srsf(f, time, smooth) gamma = np.tile(np.linspace(0, 1, M), (N, 1)) gamma = gamma.transpose() itr = 1 LL = np.zeros(max_itr) while itr <= max_itr: print("Iteration: %d" % itr) # align data fn = np.zeros((M, N)) qn = np.zeros((M, N)) for ii in range(0, N): fn[:, ii] = np.interp( (time[-1] - time[0]) * gamma[:, ii] + time[0], time, f[:, ii]) qn[:, ii] = uf.warp_q_gamma(time, q[:, ii], gamma[:, ii]) Phi = np.ones((N, Nb + 1)) for ii in range(0, N): for jj in range(1, Nb + 1): Phi[ii, jj] = trapz(qn[:, ii] * B[:, jj - 1], time) # Find alpha and beta using l_bfgs b0 = np.zeros(m * (Nb + 1)) out = fmin_l_bfgs_b(mlogit_loss, b0, fprime=mlogit_gradient, args=(Phi, Y), pgtol=1e-10, maxiter=200, maxfun=250, factr=1e-30) b = out[0] B0 = b.reshape(Nb + 1, m) alpha = B0[0, :] beta = np.zeros((M, m)) for i in range(0, m): beta[:, i] = B.dot(B0[1:Nb + 1, i]) # compute the logistic loss LL[itr - 1] = mlogit_loss(b, Phi, Y) # find gamma gamma_new = np.zeros((M, N)) if parallel: out = Parallel(n_jobs=cores)(delayed(mlogit_warp_grad)( alpha, beta, time, q[:, n], Y[n, :], delta=delta) for n in range(N)) gamma_new = np.array(out) gamma_new = gamma_new.transpose() else: for ii in range(0, N): gamma_new[:, ii] = mlogit_warp_grad(alpha, beta, time, q[:, ii], Y[ii, :], delta=delta) if norm(gamma - gamma_new) < 1e-5: break else: gamma = gamma_new itr += 1 # Last Step with centering of gam gamma = gamma_new # gamI = uf.SqrtMeanInverse(gamma) # gamI_dev = np.gradient(gamI, 1 / float(M - 1)) # beta = np.interp((time[-1] - time[0]) * gamI + time[0], time, # beta) * np.sqrt(gamI_dev) # for ii in range(0, N): # qn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, qn[:, ii]) * np.sqrt(gamI_dev) # fn[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, fn[:, ii]) # gamma[:, ii] = np.interp((time[-1] - time[0]) * gamI + time[0], # time, gamma[:, ii]) model = collections.namedtuple('model', [ 'alpha', 'beta', 'fn', 'qn', 'gamma', 'q', 'B', 'b', 'Loss', 'n_classes', 'type' ]) out = model(alpha, beta, fn, qn, gamma, q, B, b[1:-1], LL[0:itr], m, 'mlogistic') return out
def elastic_prediction(f, time, model, y=None, smooth=False): """ This function performs prediction from an elastic regression model with phase-variablity :param f: numpy ndarray of shape (M,N) of N functions with M samples :param time: vector of size M describing the sample points :param model: indentified model from elastic_regression :param y: truth, optional used to calculate SSE :rtype: tuple of numpy array :return alpha: alpha parameter of model :return beta: beta(t) of model :return fn: aligned functions - numpy ndarray of shape (M,N) of N functions with M samples :return qn: aligned srvfs - similar structure to fn :return gamma: calculated warping functions :return q: original training SRSFs :return B: basis matrix :return b: basis coefficients :return SSE: sum of squared error """ q = uf.f_to_srsf(f, time, smooth) n = q.shape[1] if model.type == 'linear' or model.type == 'logistic': y_pred = np.zeros(n) elif model.type == 'mlogistic': m = model.n_classes y_pred = np.zeros((n, m)) for ii in range(0, n): diff = model.q - q[:, ii][:, np.newaxis] dist = np.sum(np.abs(diff)**2, axis=0)**(1. / 2) q_tmp = uf.warp_q_gamma(time, q[:, ii], model.gamma[:, dist.argmin()]) if model.type == 'linear': y_pred[ii] = model.alpha + trapz(q_tmp * model.beta, time) elif model.type == 'logistic': y_pred[ii] = model.alpha + trapz(q_tmp * model.beta, time) elif model.type == 'mlogistic': for jj in range(0, m): y_pred[ii, jj] = model.alpha[jj] + trapz( q_tmp * model.beta[:, jj], time) if y is None: if model.type == 'linear': SSE = None elif model.type == 'logistic': y_pred = phi(y_pred) y_labels = np.ones(n) y_labels[y_pred < 0.5] = -1 PC = None elif model.type == 'mlogistic': y_pred = phi(y_pred.ravel()) y_pred = y_pred.reshape(n, m) y_labels = y_pred.argmax(axis=1) + 1 PC = None else: if model.type == 'linear': SSE = sum((y - y_pred)**2) elif model.type == 'logistic': y_pred = phi(y_pred) y_labels = np.ones(n) y_labels[y_pred < 0.5] = -1 TP = sum(y[y_labels == 1] == 1) FP = sum(y[y_labels == -1] == 1) TN = sum(y[y_labels == -1] == -1) FN = sum(y[y_labels == 1] == -1) PC = (TP + TN) / float(TP + FP + FN + TN) elif model.type == 'mlogistic': y_pred = phi(y_pred.ravel()) y_pred = y_pred.reshape(n, m) y_labels = y_pred.argmax(axis=1) + 1 PC = np.zeros(m) cls_set = np.arange(1, m + 1) for ii in range(0, m): cls_sub = np.delete(cls_set, ii) TP = sum(y[y_labels == (ii + 1)] == (ii + 1)) FP = sum(y[np.in1d(y_labels, cls_sub)] == (ii + 1)) TN = sum(y[np.in1d(y_labels, cls_sub)] == y_labels[np.in1d( y_labels, cls_sub)]) FN = sum(np.in1d(y[y_labels == (ii + 1)], cls_sub)) PC[ii] = (TP + TN) / float(TP + FP + FN + TN) PC = sum(y == y_labels) / float(y_labels.size) if model.type == 'linear': prediction = collections.namedtuple('prediction', ['y_pred', 'SSE']) out = prediction(y_pred, SSE) elif model.type == 'logistic': prediction = collections.namedtuple('prediction', ['y_prob', 'y_labels', 'PC']) out = prediction(y_pred, y_labels, PC) elif model.type == 'mlogistic': prediction = collections.namedtuple('prediction', ['y_prob', 'y_labels', 'PC']) out = prediction(y_pred, y_labels, PC) return out
def pairwise_align_bayes(f1i, f2i, time, mcmcopts=None): """ This function aligns two functions using Bayesian framework. It will align f2 to f1. It is based on mapping warping functions to a hypersphere, and a subsequent exponential mapping to a tangent space. In the tangent space, the Z-mixture pCN algorithm is used to explore both local and global structure in the posterior distribution. The Z-mixture pCN algorithm uses a mixture distribution for the proposal distribution, controlled by input parameter zpcn. The zpcn$betas must be between 0 and 1, and are the coefficients of the mixture components, with larger coefficients corresponding to larger shifts in parameter space. The zpcn["probs"] give the probability of each shift size. Usage: out = pairwise_align_bayes(f1i, f2i, time) out = pairwise_align_bayes(f1i, f2i, time, mcmcopts) :param f1i: vector defining M samples of function 1 :param f2i: vector defining M samples of function 2 :param time: time vector of length M :param mcmopts: dict of mcmc parameters :type mcmcopts: dict default mcmc options: tmp = {"betas":np.array([0.5,0.5,0.005,0.0001]),"probs":np.array([0.1,0.1,0.7,0.1])} mcmcopts = {"iter":2*(10**4) ,"burnin":np.minimum(5*(10**3),2*(10**4)//2), "alpha0":0.1, "beta0":0.1,"zpcn":tmp,"propvar":1, "initcoef":np.repeat(0,20), "npoints":200, "extrainfo":True} :rtype collection containing :return f2_warped: aligned f2 :return gamma: warping function :return g_coef: final g_coef :return psi: final psi :return sigma1: final sigma if extrainfo :return accept: accept of psi samples :return betas_ind :return logl: log likelihood :return gamma_mat: posterior gammas :return gamma_stats: posterior gamma stats :return xdist: phase distance posterior :return ydist: amplitude distance posterior) """ if mcmcopts is None: tmp = { "betas": np.array([0.5, 0.5, 0.005, 0.0001]), "probs": np.array([0.1, 0.1, 0.7, 0.1]) } mcmcopts = { "iter": 2 * (10**4), "burnin": np.minimum(5 * (10**3), 2 * (10**4) // 2), "alpha0": 0.1, "beta0": 0.1, "zpcn": tmp, "propvar": 1, "initcoef": np.repeat(0, 20), "npoints": 200, "extrainfo": True } if f1i.shape[0] != f2i.shape[0]: raise Exception('Length of f1 and f2 must be equal') if f1i.shape[0] != time.shape[0]: raise Exception('Length of f1 and time must be equal') if mcmcopts["zpcn"]["betas"].shape[0] != mcmcopts["zpcn"]["probs"].shape[0]: raise Exception('In zpcn, betas must equal length of probs') if np.mod(mcmcopts["initcoef"].shape[0], 2) != 0: raise Exception('Length of mcmcopts.initcoef must be even') # Number of sig figs to report in gamma_mat SIG_GAM = 13 iter = mcmcopts["iter"] # parameter settings pw_sim_global_burnin = mcmcopts["burnin"] valid_index = np.arange(pw_sim_global_burnin - 1, iter) pw_sim_global_Mg = mcmcopts["initcoef"].shape[0] // 2 g_coef_ini = mcmcopts["initcoef"] numSimPoints = mcmcopts["npoints"] pw_sim_global_domain_par = np.linspace(0, 1, numSimPoints) g_basis = uf.basis_fourier(pw_sim_global_domain_par, pw_sim_global_Mg, 1) sigma1_ini = 1 zpcn = mcmcopts["zpcn"] pw_sim_global_sigma_g = mcmcopts["propvar"] def propose_g_coef(g_coef_curr): pCN_beta = zpcn["betas"] pCN_prob = zpcn["probs"] probm = np.insert(np.cumsum(pCN_prob), 0, 0) z = np.random.rand() result = {"prop": g_coef_curr, "ind": 1} for i in range(0, pCN_beta.shape[0]): if z <= probm[i + 1] and z > probm[i]: g_coef_new = normal( 0, pw_sim_global_sigma_g / np.repeat(np.arange(1, pw_sim_global_Mg + 1), 2)) result["prop"] = np.sqrt( 1 - pCN_beta[i]**2) * g_coef_curr + pCN_beta[i] * g_coef_new result["ind"] = i return result # normalize time to [0,1] time = (time - time.min()) / (time.max() - time.min()) timet = np.linspace(0, 1, numSimPoints) f1 = uf.f_predictfunction(f1i, timet, 0) f2 = uf.f_predictfunction(f2i, timet, 0) # srsf transformation q1 = uf.f_to_srsf(f1, timet) q1i = uf.f_to_srsf(f1i, time) q2 = uf.f_to_srsf(f2, timet) tmp = uf.f_exp1(uf.f_basistofunction(g_basis["x"], 0, g_coef_ini, g_basis)) if tmp.min() < 0: raise Exception("Invalid initial value of g") # result vectors g_coef = np.zeros((iter, g_coef_ini.shape[0])) sigma1 = np.zeros(iter) logl = np.zeros(iter) SSE = np.zeros(iter) accept = np.zeros(iter, dtype=bool) accept_betas = np.zeros(iter) # init g_coef_curr = g_coef_ini sigma1_curr = sigma1_ini SSE_curr = f_SSEg_pw( uf.f_basistofunction(g_basis["x"], 0, g_coef_ini, g_basis), q1, q2) logl_curr = f_logl_pw( uf.f_basistofunction(g_basis["x"], 0, g_coef_ini, g_basis), q1, q2, sigma1_ini**2, SSE_curr) g_coef[0, :] = g_coef_ini sigma1[0] = sigma1_ini SSE[0] = SSE_curr logl[0] = logl_curr # update the chain for iter-1 times for m in tqdm(range(1, iter)): # update g g_coef_curr, tmp, SSE_curr, accepti, zpcnInd = f_updateg_pw( g_coef_curr, g_basis, sigma1_curr**2, q1, q2, SSE_curr, propose_g_coef) # update sigma1 newshape = q1.shape[0] / 2 + mcmcopts["alpha0"] newscale = 1 / 2 * SSE_curr + mcmcopts["beta0"] sigma1_curr = np.sqrt(1 / np.random.gamma(newshape, 1 / newscale)) logl_curr = f_logl_pw( uf.f_basistofunction(g_basis["x"], 0, g_coef_curr, g_basis), q1, q2, sigma1_curr**2, SSE_curr) # save updates to results g_coef[m, :] = g_coef_curr sigma1[m] = sigma1_curr SSE[m] = SSE_curr if mcmcopts["extrainfo"]: logl[m] = logl_curr accept[m] = accepti accept_betas[m] = zpcnInd # calculate posterior mean of psi pw_sim_est_psi_matrix = np.zeros((numSimPoints, valid_index.shape[0])) for k in range(0, valid_index.shape[0]): g_temp = uf.f_basistofunction(g_basis["x"], 0, g_coef[valid_index[k], :], g_basis) psi_temp = uf.f_exp1(g_temp) pw_sim_est_psi_matrix[:, k] = psi_temp result_posterior_psi_simDomain = uf.f_psimean(pw_sim_global_domain_par, pw_sim_est_psi_matrix) # resample to same number of points as the input f1 and f2 interp = interp1d(np.linspace(0, 1, result_posterior_psi_simDomain.shape[0]), result_posterior_psi_simDomain, fill_value="extrapolate") result_posterior_psi = interp(np.linspace(0, 1, f1i.shape[0])) # transform posterior mean of psi to gamma result_posterior_gamma = uf.f_phiinv(result_posterior_psi) result_posterior_gamma = uf.norm_gam(result_posterior_gamma) # warped f2 f2_warped = uf.warp_f_gamma(time, f2i, result_posterior_gamma) if mcmcopts["extrainfo"]: M, N = pw_sim_est_psi_matrix.shape gamma_mat = np.zeros((time.shape[0], N)) one_v = np.ones(M) Dx = np.zeros(N) Dy = Dx for ii in range(0, N): interp = interp1d(np.linspace( 0, 1, result_posterior_psi_simDomain.shape[0]), pw_sim_est_psi_matrix[:, ii], fill_value="extrapolate") result_i = interp(time) tmp = uf.f_phiinv(result_i) gamma_mat[:, ii] = uf.norm_gam(tmp) v, theta = geo.inv_exp_map(one_v, pw_sim_est_psi_matrix[:, ii]) Dx[ii] = np.sqrt(trapz(v**2, pw_sim_global_domain_par)) q2warp = uf.warp_q_gamma(pw_sim_global_domain_par, q2, gamma_mat[:, ii]) Dy[ii] = np.sqrt(trapz((q1i - q2warp)**2, time)) gamma_stats = uf.statsFun(gamma_mat) results_o = collections.namedtuple('align_bayes', [ 'f2_warped', 'gamma', 'g_coef', 'psi', 'sigma1', 'accept', 'betas_ind', 'logl', 'gamma_mat', 'gamma_stats', 'xdist', 'ydist' ]) out = results_o(f2_warped, result_posterior_gamma, g_coef, result_posterior_psi, sigma1, accept[1:], accept_betas[1:], logl, gamma_mat, gamma_stats, Dx, Dy) return (out)
def find_C_sub(time, qhat, gamhat, q0): tmp = uf.warp_q_gamma(time, qhat, uf.invertGamma(gamhat)) d = trapz((tmp - q0) * (tmp - q0), time) return d