def _normal_lml(self): self._update() m = self.m() ttau = self._sitelik_tau teta = self._sitelik_eta # NEW PHENOTYPE y = teta.copy() # NEW MEAN m = ttau * m # NEW COVARIANCE K = self.K() K = ddot(ttau, ddot(K, ttau, left=False), left=True) sum2diag(K, ttau, out=K) (Q, S0) = economic_qs(K) Q0, Q1 = Q from ...lmm import FastLMM from numpy import newaxis fastlmm = FastLMM(y, Q0, Q1, S0, covariates=m[:, newaxis]) fastlmm.learn(progress=False) return fastlmm.lml()
def lml_derivative_over_cov(self, dQS): from numpy_sugar.linalg import cho_solve, ddot, dotd self._update() L = self._posterior.L() Q = self._posterior.cov["QS"][0][0] ttau = self._site.tau teta = self._site.eta diff = teta - ttau * self._posterior.mean v0 = dot(dQS[0][0], dQS[1] * dot(dQS[0][0].T, diff)) v1 = ttau * dot(Q, cho_solve(L, dot(Q.T, diff))) dlml = 0.5 * dot(diff, v0) dlml -= dot(v0, v1) dlml += 0.5 * dot(v1, dot(dQS[0][0], dQS[1] * dot(dQS[0][0].T, v1))) dqs = ddot(dQS[1], dQS[0][0].T, left=True) diag = dotd(dQS[0][0], dqs) dlml -= 0.5 * sum(ttau * diag) tmp = cho_solve(L, dot(ddot(Q.T, ttau, left=False), dQS[0][0])) dlml += 0.5 * sum(ttau * dotd(Q, dot(tmp, dqs))) return dlml
def _gradient_over_v(self): self._update() A = self._A() Q = self._Q S = self._S C = self._C() m = self.m() delta = self.delta v = self.v teta = self._sitelik_eta AQ = ddot(A, Q, left=True) SQt = ddot(S, Q.T, left=True) Am = A * m Em = Am - A * self._QBiQtAm() Cteta = C * teta Eu = Cteta - A * self._QBiQtCteta() u = Em - Eu uBiQtAK0, uBiQtAK1 = self._uBiQtAK() out = dot(u, self._Kdot(u)) out /= v out -= (1 - delta) * trace2(AQ, SQt) out -= delta * A.sum() out += (1 - delta) * trace2(AQ, uBiQtAK0) out += delta * trace2(AQ, uBiQtAK1) out /= 2 return out
def _uBiQtAK(self): BiQt = self._BiQt() S = self._S Q = self._Q BiQtA = ddot(BiQt, self._A(), left=False) BiQtAQS = dot(BiQtA, Q) ddot(BiQtAQS, S, left=False, out=BiQtAQS) return dot(BiQtAQS, Q.T), BiQtA
def _gradient_over_both(self): self._update() v = self.v delta = self.delta Q = self._Q S = self._S A = self._A() AQ = ddot(A, Q, left=True) SQt = ddot(S, Q.T, left=True) BiQt = self._BiQt() uBiQtAK0, uBiQtAK1 = self._uBiQtAK() C = self._C() m = self.m() teta = self._sitelik_eta Q = self._Q As = A.sum() Am = A * m Em = Am - A * self._QBiQtAm() Cteta = C * teta Eu = Cteta - A * self._QBiQtCteta() u = Em - Eu uKu = dot(u, self._Kdot(u)) tr1 = trace2(AQ, uBiQtAK0) tr2 = trace2(AQ, uBiQtAK1) dv = uKu / v dv -= (1 - delta) * trace2(AQ, SQt) dv -= delta * As dv += (1 - delta) * tr1 dv += delta * tr2 dv /= 2 dd = delta / (1 - delta) ddelta = -tr1 ddelta -= dd * tr2 ddelta += trace2(AQ, ddot(BiQt, A, left=False)) * (dd + 1) ddelta += (dd + 1) * dot(u, u) ddelta += trace2(AQ, SQt) ddelta -= As ddelta *= v ddelta -= uKu / (1 - delta) ddelta /= 2 v = asarray([dv, ddelta]) if not is_all_finite(v): raise ValueError("LML gradient should not be %s." % str(v)) return v
def QSQtATQLQtA(self): from numpy_sugar.linalg import ddot, dotd if self._QSQtATQLQtA_cache is not None: return self._QSQtATQLQtA_cache LQt = self.LQt() A = self.A Q = self._cov["QS"][0][0] LQtA = ddot(LQt, A) AQ = self.AQ() QS = self.QS() T = self._site.tau self._QSQtATQLQtA_cache = dotd(QS, dot(dot(ddot(AQ.T, T), Q), LQtA)) return self._QSQtATQLQtA_cache
def compute(self, covariates, X): A = self._A L = self._L Q = self._Q C = self._C AMs0 = ddot(A, covariates, left=True) dens0 = AMs0 - ddot(A, dot(Q, cho_solve(L, dot(Q.T, AMs0))), left=True) noms0 = dot(self._beta_nom, covariates) AMs1 = ddot(A, X, left=True) dens1 = AMs1 - ddot(A, dot(Q, cho_solve(L, dot(Q.T, AMs1))), left=True) noms1 = dot(self._beta_nom, X) row00 = sum(covariates * dens0, 0) row01 = sum(covariates * dens1, 0) row11 = sum(X * dens1, 0) betas0 = noms0 * row11 betas0 -= noms1 * row01 betas1 = -noms0 * row01 betas1 += noms1 * row00 denom = row00 * row11 denom -= row01**2 with errstate(divide='ignore', invalid='ignore'): betas0 /= denom betas1 /= denom betas0 = nan_to_num(betas0) betas1 = nan_to_num(betas1) ms = dot(covariates, betas0[newaxis, :]) ms += X * betas1 QBiQtCteta = self._QBiQtCteta teta = self._teta Am = ddot(A, ms, left=True) w4 = dot(C * teta, ms) w4 -= dot(QBiQtCteta, Am) QBiQtAm = dot(Q, cho_solve(L, dot(Q.T, Am))) w5 = -sum(Am * ms, 0) w5 += sum(Am * QBiQtAm, 0) w5 /= 2 lmls = self._lml_const + w4 + w5 return (lmls, betas1)
def _LhD(self): """ Implements Lₕ and D. Returns ------- Lh : ndarray Uₕᵀ S₁⁻½ U₁ᵀ. D : ndarray (Sₕ ⊗ Sₓ + Iₕₓ)⁻¹. """ from numpy_sugar.linalg import ddot self._init_svd() if self._cache["LhD"] is not None: return self._cache["LhD"] S1, U1 = self._C1.eigh() U1S1 = ddot(U1, 1 / sqrt(S1)) Sh, Uh = eigh(U1S1.T @ self.C0.value() @ U1S1) self._cache["LhD"] = { "Lh": (U1S1 @ Uh).T, "D": 1 / (kron(Sh, self._Sx) + 1), "De": 1 / (kron(Sh, self._Sxe) + 1), } return self._cache["LhD"]
def update(self): from numpy_sugar.linalg import ddot, dotd self._flush_cache() Q = self._cov["QS"][0][0] K = dot(self.QS(), Q.T) BiQt = self.LQt() TK = ddot(self._site.tau, K, left=True) BiQtTK = dot(BiQt, TK) self.tau[:] = K.diagonal() self.tau -= dotd(Q, BiQtTK) self.tau[:] = 1 / self.tau if not all(self.tau >= 0.0): raise RuntimeError("'tau' has to be non-negative.") self.eta[:] = dot(K, self._site.eta) self.eta[:] += self._mean self.eta[:] -= dot(Q, dot(BiQtTK, self._site.eta)) self.eta[:] -= dot(Q, dot(BiQt, self._site.tau * self._mean)) self.eta *= self.tau
def beta_covariance(self): """ Estimates the covariance-matrix of the optimal beta. Returns ------- beta-covariance : ndarray (Xᵀ(s((1-𝛿)K + 𝛿I))⁻¹X)⁻¹. References ---------- .. Rencher, A. C., & Schaalje, G. B. (2008). Linear models in statistics. John Wiley & Sons. """ from numpy_sugar.linalg import ddot tX = self._X["tX"] Q = concatenate(self._QS[0], axis=1) S0 = self._QS[1] D = self.v0 * S0 + self.v1 D = D.tolist() + [self.v1] * (len(self._y) - len(D)) D = asarray(D) A = inv(tX.T @ (Q @ ddot(1 / D, Q.T @ tX))) VT = self._X["VT"] H = lstsq(VT, A, rcond=None)[0] return lstsq(VT, H.T, rcond=None)[0]
def update(self): from numpy_sugar.linalg import ddot, dotd self._flush_cache() s = self._cov["scale"] d = self._cov["delta"] Q = self._cov["QS"][0][0] A = self.A LQt = self.LQt() T = self._site.tau E = self._site.eta AQ = self.AQ() QS = self.QS() LQtA = ddot(LQt, A) D = self.QSQtATQLQtA() self.tau[:] = s * (1 - d) * dotd(QS, AQ.T) self.tau -= s * (1 - d) * D * (1 - d) self.tau += s * d * A self.tau -= s * d * dotd(self.ATQ(), LQtA) * (1 - d) self.tau **= -1 v = s * (1 - d) * dot(Q, dot(QS.T, E)) + s * d * E + self._mean self.eta[:] = A * v self.eta -= dot(AQ, dot(LQtA, T * v)) * (1 - d) self.eta *= self.tau
def __init__(self, A=None, USVt=None): from numpy_sugar.linalg import ddot, economic_svd if A is None and USVt is None: raise ValueError("Both `A` and `USVt` cannot be `None`.") if A is None: self._US = ddot(USVt[0], USVt[1]) self._Vt = USVt[2] self._A = self._US @ self._Vt else: USVt = economic_svd(A) self._US = ddot(USVt[0], USVt[1]) self._Vt = USVt[2] self._A = A self._rank = len(USVt[1])
def ATQ(self): from numpy_sugar.linalg import ddot if self._TAQ_cache is not None: return self._TAQ_cache self._TAQ_cache = ddot(self._site.tau, self.AQ()) return self._TAQ_cache
def _covariate_setup(self, M): self._M = M SVD = economic_svd(M) self._svd_U = SVD[0] self._svd_S12 = sqrt(SVD[1]) self._svd_V = SVD[2] self._tM = ddot(self._svd_U, self._svd_S12, left=False) if self.__tbeta is not None: self.__tbeta = resize(self.__tbeta, self._tM.shape[1])
def _optimal_tbeta_denom(self): L = self._L() Q = self._Q AM = ddot(self._A(), self._tM, left=True) QBiQtAM = dot(Q, cho_solve(L, dot(Q.T, AM))) v = dot(self._tM.T, AM) - dot(AM.T, QBiQtAM) if not is_all_finite(v): raise ValueError("tbeta_denom should not be %s." % str(v)) return v
def QS(self): from numpy_sugar.linalg import ddot if self._QS_cache is not None: return self._QS_cache Q = self._cov["QS"][0][0] S = self._cov["QS"][1] self._QS_cache = ddot(Q, S) return self._QS_cache
def AQ(self): from numpy_sugar.linalg import ddot if self._AQ_cache is not None: return self._AQ_cache Q = self._cov["QS"][0][0] A = self.A self._AQ_cache = ddot(A, Q) return self._AQ_cache
def _gradient_over_delta(self): self._update() v = self.v delta = self.delta Q = self._Q S = self._S A = self._A() C = self._C() m = self.m() teta = self._sitelik_eta Am = A * m Em = Am - A * self._QBiQtAm() Cteta = C * teta Eu = Cteta - A * self._QBiQtCteta() u = Em - Eu AQ = ddot(A, Q, left=True) SQt = ddot(S, Q.T, left=True) BiQt = self._BiQt() uBiQtAK0, uBiQtAK1 = self._uBiQtAK() out = -trace2(AQ, uBiQtAK0) out -= (delta / (1 - delta)) * trace2(AQ, uBiQtAK1) out += trace2(AQ, ddot(BiQt, A, left=False)) * \ ((delta / (1 - delta)) + 1) out += (1 + delta / (1 - delta)) * dot(u, u) out += trace2(AQ, SQt) + (delta / (1 - delta)) * A.sum() out -= (1 + delta / (1 - delta)) * A.sum() out *= v out -= dot(u, self._Kdot(u)) / (1 - delta) return out / 2
def Ge(self): """ Result of US from the SVD decomposition G = USVᵀ. """ from numpy_sugar.linalg import ddot from scipy.linalg import svd U, S, _ = svd(self._G, full_matrices=False, check_finite=False) if U.shape[1] < self._G.shape[1]: return ddot(U, S) return self._G
def value(self): r"""Log of the marginal likelihood. Formally, .. math:: - \frac{n}{2}\log{2\pi} - \frac{1}{2} \log{\left| v_0 \mathrm K + v_1 \mathrm I + \tilde{\Sigma} \right|} - \frac{1}{2} \left(\tilde{\boldsymbol\mu} - \mathrm X\boldsymbol\beta\right)^{\intercal} \left( v_0 \mathrm K + v_1 \mathrm I + \tilde{\Sigma} \right)^{-1} \left(\tilde{\boldsymbol\mu} - \mathrm X\boldsymbol\beta\right) Returns ------- float :math:`\log{p(\tilde{\boldsymbol\mu})}` """ from numpy_sugar.linalg import ddot, sum2diag if self._cache["value"] is not None: return self._cache["value"] scale = exp(self.logscale) delta = 1 / (1 + exp(-self.logitdelta)) v0 = scale * (1 - delta) v1 = scale * delta mu = self.eta / self.tau n = len(mu) if self._QS is None: K = zeros((n, n)) else: Q0 = self._QS[0][0] S0 = self._QS[1] K = dot(ddot(Q0, S0), Q0.T) A = sum2diag(sum2diag(v0 * K, v1), 1 / self.tau) m = mu - self.mean() v = -n * log(2 * pi) v -= slogdet(A)[1] v -= dot(m, solve(A, m)) self._cache["value"] = v / 2 return self._cache["value"]
def gradient(self): from numpy_sugar.linalg import ddot, sum2diag if self._cache["grad"] is not None: return self._cache["grad"] scale = exp(self.logscale) delta = 1 / (1 + exp(-self.logitdelta)) v0 = scale * (1 - delta) v1 = scale * delta mu = self.eta / self.tau n = len(mu) if self._QS is None: K = zeros((n, n)) else: Q0 = self._QS[0][0] S0 = self._QS[1] K = dot(ddot(Q0, S0), Q0.T) A = sum2diag(sum2diag(v0 * K, v1), 1 / self.tau) X = self._X m = mu - self.mean() g = dict() Aim = solve(A, m) g["beta"] = dot(m, solve(A, X)) Kd0 = sum2diag((1 - delta) * K, delta) Kd1 = sum2diag(-scale * K, scale) g["scale"] = -trace(solve(A, Kd0)) g["scale"] += dot(Aim, dot(Kd0, Aim)) g["scale"] *= 1 / 2 g["delta"] = -trace(solve(A, Kd1)) g["delta"] += dot(Aim, dot(Kd1, Aim)) g["delta"] *= 1 / 2 ed = exp(-self.logitdelta) es = exp(self.logscale) grad = dict() grad["logitdelta"] = g["delta"] * (ed / (1 + ed)) / (1 + ed) grad["logscale"] = g["scale"] * es grad["beta"] = g["beta"] self._cache["grad"] = grad return self._cache["grad"]
def covariance(self): r"""Covariance of the prior. Returns ------- :class:`numpy.ndarray` :math:`v_0 \mathrm K + v_1 \mathrm I`. """ from numpy_sugar.linalg import ddot, sum2diag Q0 = self._QS[0][0] S0 = self._QS[1] return sum2diag(dot(ddot(Q0, self.v0 * S0), Q0.T), self.v1)
def get_normal_likelihood_trick(self): # Covariance: nK = K + \tilde\Sigma = K + 1/self._sitelik_tau # via (K + 1/self._sitelik_tau)^{-1} = A1 - A1QB1^-1QTA1 # Mean: \mathbf m # New phenotype: \tilde\mu # # I.e.: \tilde\mu \sim N(\mathbf m, K + \tilde\Sigma) # # # We transform the above Normal in an equivalent but more robust # one: \tilde\y \sim N(\tilde\m, \tilde\nK + \Sigma^{-1}) # # \tilde\y = \tilde\Sigma^{-1} \tilde\mu # \tilde\m = \tilde\Sigma^{-1} \tilde\m # \tilde\nK = \tilde\Sigma^{-1} \nK \tilde\Sigma^{-1} m = self.m() ttau = self._sitelik_tau teta = self._sitelik_eta # NEW PHENOTYPE y = teta.copy() # NEW MEAN m = ttau * m # NEW COVARIANCE K = self.K() K = ddot(ttau, ddot(K, ttau, left=False), left=True) sum2diag(K, ttau, out=K) (Q, S0) = economic_qs(K) Q0, Q1 = Q from ...lmm import FastLMM from numpy import newaxis fastlmm = FastLMM(y, Q0, Q1, S0, covariates=m[:, newaxis]) fastlmm.learn(progress=False) return fastlmm.get_normal_likelihood_trick()
def _L(self): r"""Returns the Cholesky factorization of :math:`\mathcal B`. .. math:: \mathcal B = \mathrm Q^{\intercal}\mathcal A\mathrm Q (\sigma_b^2 \mathrm S)^{-1} """ Q = self._Q A = self._A() B = dot(Q.T, ddot(A, Q, left=True)) sum2diag(B, 1. / (self.sigma2_b * self._S), out=B) return cho_factor(B, lower=True)[0]
def _fit_lmm_multi_trait(self, verbose): from numpy import sqrt, asarray from glimix_core.lmm import Kron2Sum from numpy_sugar.linalg import economic_qs, ddot X = asarray(self._M, float) QS = economic_qs(self._covariance[0]._K) G = ddot(QS[0][0], sqrt(QS[1])) lmm = Kron2Sum(self._y, self._mean.A, X, G, rank=1, restricted=True) lmm.fit(verbose=verbose) self._glmm = lmm self._covariance[0]._set_kron2sum(lmm) self._covariance[1]._set_kron2sum(lmm) self._mean.B = lmm.B
def covariance(self): """ Covariance of the prior. Returns ------- covariance : ndarray v₀𝙺 + v₁𝙸. """ from numpy_sugar.linalg import ddot, sum2diag Q0 = self._Q0 S0 = self._S0 return sum2diag(dot(ddot(Q0, self.v0 * S0), Q0.T), self.v1)
def scan(self, M): """ LML, fixed-effect sizes, and scale of the candidate set. Parameters ---------- M : array_like Fixed-effects set. Returns ------- lml : float Log of the marginal likelihood. effsizes0 : ndarray Covariates fixed-effect sizes. effsizes0_se : ndarray Covariates fixed-effect size standard errors. effsizes1 : ndarray Candidate set fixed-effect sizes. effsizes1_se : ndarray Candidate fixed-effect size standard errors. scale : ndarray Optimal scale. """ from numpy_sugar.linalg import ddot from numpy_sugar import is_all_finite M = asarray(M, float) if M.shape[1] == 0: return { "lml": self.null_lml(), "effsizes0": self.null_beta, "effsizes0_se": self.null_beta_se, "effsizes1": empty((0)), "effsizes1_se": empty((0)), "scale": self.null_scale, } if not is_all_finite(M): raise ValueError("M parameter has non-finite elements.") MTQ = [dot(M.T, Q) for Q in self._QS[0] if Q.size > 0] yTBM = [dot(i, j.T) for (i, j) in zip(self._yTQDi, MTQ)] XTBM = [dot(i, j.T) for (i, j) in zip(self._XTQDi, MTQ)] D = self._D MTBM = [ddot(i, 1 / j) @ i.T for i, j in zip(MTQ, D) if j.min() > 0] return self._multicovariate_set(yTBM, XTBM, MTBM)
def LU(self): r"""LU factor of :math:`\mathrm B`. .. math:: \mathrm B = \mathrm Q^{\intercal}\tilde{\mathrm{T}}\mathrm Q + \mathrm{S}^{-1} """ from numpy_sugar.linalg import ddot, sum2diag if self._LU_cache is not None: return self._LU_cache s = self._cov["scale"] d = self._cov["delta"] Q = self._cov["QS"][0][0] S = self._cov["QS"][1] ddot(self.A * self._site.tau, Q, left=True, out=self._NxR) B = dot(Q.T, self._NxR, out=self._RxR) B *= 1 - d sum2diag(B, 1.0 / S / s, out=B) self._LU_cache = lu_factor(B, overwrite_a=True, check_finite=False) return self._LU_cache
def _get_mvn_restricted(y, X, G): SVD = economic_svd(X) tX = ddot(SVD[0], SVD[1]) def mvn_restricted(beta, v0, v1, y, X, K0): n = K0.shape[0] m = X @ beta K = v0 * K0 + v1 * eye(K0.shape[0]) lml = st.multivariate_normal(m, K).logpdf(y) lml += slogdet(tX.T @ tX)[1] / 2 - slogdet(tX.T @ solve(K, tX))[1] / 2 lml += n * log(2 * pi) / 2 lml -= (n - tX.shape[1]) * log(2 * pi) / 2 return lml return mvn_restricted
def L(self): r"""Cholesky decomposition of :math:`\mathrm B`. .. math:: \mathrm B = \mathrm Q^{\intercal}\tilde{\mathrm{T}}\mathrm Q + \mathrm{S}^{-1} """ from numpy_sugar.linalg import ddot, sum2diag if self._L_cache is not None: return self._L_cache s = self._cov["scale"] d = self._cov["delta"] Q = self._cov["QS"][0][0] S = self._cov["QS"][1] ddot(self.A * self._site.tau, Q, left=True, out=self._NxR) B = dot(Q.T, self._NxR, out=self._RxR) B *= 1 - d sum2diag(B, 1.0 / S / s, out=B) self._L_cache = _cho_factor(B) return self._L_cache