def opg_subspace(X, f, weights): """Estimate active subspace with local linear models. This approach is related to the sufficient dimension reduction method known sometimes as the outer product of gradient method. See the 2001 paper 'Structure adaptive approach for dimension reduction' from Hristache, et al. Parameters ---------- X : ndarray M-by-m matrix of input samples, oriented as rows f : ndarray M-by-1 vector of output samples corresponding to the rows of `X` weights : ndarray M-by-1 weight vector, corresponds to numerical quadrature rule used to estimate matrix whose eigenspaces define the active subspace Returns ------- e : ndarray m-by-1 vector of eigenvalues W : ndarray m-by-m orthogonal matrix of eigenvectors """ X, f, M, m = process_inputs_outputs(X, f) # Obtain gradient approximations using local linear regressions df = local_linear_gradients(X, f, weights=weights) # Use gradient approximations to compute active subspace opg_weights = np.ones((df.shape[0], 1)) / df.shape[0] e, W = active_subspace(df, opg_weights) return e, W
def train(self, X, f, weights=None): """Train the least-squares-fit polynomial approximation. Parameters ---------- X : ndarray an ndarray of training points for the polynomial approximation. The shape is M-by-m, where m is the number of dimensions. f : ndarray an ndarray of function values used to train the polynomial approximation. The shape of `f` is M-by-1. weights : ndarray, optional an ndarray of weights for the least-squares. (default is None, which means uniform weights) Notes ----- This method sets all the attributes of the class for use in the `predict` method. """ X, f, M, m = process_inputs_outputs(X, f) # check that there are enough points to train the polynomial if M < comb(self.N + m, m): raise Exception( 'Not enough points to fit response surface of order {:d}'. format(self.N)) B, indices = polynomial_bases(X, self.N) p = B.shape[1] if weights is not None: B, f = weights * B, weights * f poly_weights = np.linalg.lstsq(B, f, rcond=None)[0] Rsqr = 1.0 - (np.linalg.norm(np.dot(B, poly_weights) - f)**2 / (M * np.var(f))) # store data self.X, self.f = X, f self.poly_weights = poly_weights.reshape((p, 1)) self.Rsqr = Rsqr # organize linear and quadratic coefficients self.g = poly_weights[1:m + 1].copy().reshape((m, 1)) if self.N > 1: H = np.zeros((m, m)) for i in range(m + 1, int(m + 1 + comb(m + 1, 2))): ind = indices[i, :] loc = np.nonzero(ind != 0)[0] if loc.size == 1: H[loc, loc] = 2.0 * poly_weights[i] elif loc.size == 2: H[loc[0], loc[1]] = poly_weights[i] H[loc[1], loc[0]] = poly_weights[i] else: raise Exception('Error creating quadratic coefficients.') self.H = H
def minimize(asrs, X, f): """Minimize a response surface constructed with the active subspace. Parameters ---------- asrs : ActiveSubspaceResponseSurface a trained response_surfaces.ActiveSubspaceResponseSurface X : ndarray input points used to train the MinVariableMap f : ndarray simulation outputs used to train the MinVariableMap Returns ------- xstar : ndarray the estimated minimizer of the function modeled by the ActiveSubspaceResponseSurface `asrs` fstar : float the estimated minimum of the function modeled by `asrs` Notes ----- This function has two stages. First it uses the scipy.optimize package to minimize the response surface of the active variables. Then it trains a MinVariableMap with the given input/output pairs, which it uses to map the minimizer back to the space of simulation inputs. This is very heuristic. """ X, f, M, m = process_inputs_outputs(X, f) # ActiveVariableDomain avdom = asrs.avmap.domain # wrappers def avfun(y): f = asrs.predict_av(y.reshape((1, y.size)))[0] return f[0, 0] def avdfun(y): df = asrs.gradient_av(y.reshape((1, y.size))) return df.reshape((y.size, )) if isinstance(avdom, UnboundedActiveVariableDomain): mvm = UnboundedMinVariableMap(avdom) elif isinstance(avdom, BoundedActiveVariableDomain): mvm = BoundedMinVariableMap(avdom) else: raise Exception('There is a problem with the avmap.domain.') ystar, fstar = av_minimize(avfun, avdom, avdfun=avdfun) mvm.train(X, f) xstar = mvm.inverse(ystar)[0] return xstar, fstar
def qphd_subspace(X, f, weights): """Estimate active subspace with global quadratic model. This approach is similar to Ker-Chau Li's approach for principal Hessian directions based on a global quadratic model of the data. In contrast to Li's approach, this method uses the average outer product of the gradient of the quadratic model, as opposed to just its Hessian. Parameters ---------- X : ndarray M-by-m matrix of input samples, oriented as rows f : ndarray M-by-1 vector of output samples corresponding to the rows of `X` weights : ndarray M-by-1 weight vector, corresponds to numerical quadrature rule used to estimate matrix whose eigenspaces define the active subspace Returns ------- e : ndarray m-by-1 vector of eigenvalues W : ndarray m-by-m orthogonal matrix of eigenvectors """ X, f, M, m = process_inputs_outputs(X, f) # check if the points are uniform or Gaussian, set 2nd moment if np.amax(X) > 1.0 or np.amin(X) < -1.0: gamma = 1.0 else: gamma = 1.0 / 3.0 # compute a quadratic approximation pr = PolynomialApproximation(2) pr.train(X, f, weights) # get regression coefficients b, A = pr.g, pr.H # compute C C = np.outer(b, b.transpose()) + gamma * np.dot(A, A.transpose()) return sorted_eigh(C)
def ols_subspace(X, f, weights): """Estimate one-dimensional subspace with global linear model. Parameters ---------- X : ndarray M-by-m matrix of input samples, oriented as rows f : ndarray M-by-1 vector of output samples corresponding to the rows of `X` weights : ndarray M-by-1 weight vector, corresponds to numerical quadrature rule used to estimate matrix whose eigenspaces define the active subspace Returns ------- e : ndarray m-by-1 vector of eigenvalues W : ndarray m-by-m orthogonal matrix of eigenvectors Notes ----- Although the method returns a full set of eigenpairs (to be consistent with the other subspace functions), only the first eigenvalue will be nonzero, and only the first eigenvector will have any relationship to the input parameters. The remaining m-1 eigenvectors are only orthogonal to the first. """ X, f, M, m = process_inputs_outputs(X, f) # solve weighted least squares A = np.hstack((np.ones((M, 1)), X)) * np.sqrt(weights) b = f * np.sqrt(weights) u = np.linalg.lstsq(A, b, rcond=None)[0] w = u[1:].reshape((m, 1)) # compute rank-1 C C = np.dot(w, w.transpose()) return sorted_eigh(C)
def train(self, X, f): """Train the global quadratic for the regularization. Parameters ---------- X : ndarray input points used to train a global quadratic used in the `regularize_z` function f : ndarray simulation outputs used to train a global quadratic in the `regularize_z` function """ X, f, M, m = process_inputs_outputs(X, f) W1, W2 = self.domain.subspaces.W1, self.domain.subspaces.W2 m, n = W1.shape W = self.domain.subspaces.eigenvecs # train quadratic surface on p>n active vars if m - n > 2: p = n + 2 else: p = n + 1 Yp = np.dot(X, W[:, :p]) pr = PolynomialApproximation(N=2) pr.train(Yp, f) br, Ar = pr.g, pr.H # get coefficients b = np.dot(W[:, :p], br) A = np.dot(W[:, :p], np.dot(Ar, W[:, :p].T)) # some private attributes used in the regularize_z function self._bz = np.dot(W2.T, b) self._zAy = np.dot(W2.T, np.dot(A, W1)) self._zAz = np.dot(W2.T, np.dot(A, W2)) + 0.01 * np.eye(m - n)
def train(self, X, f, v=None, e=None): """Train the radial basis approximation. Parameters ---------- X : ndarray an ndarray of training points for the polynomial approximation. The shape is M-by-m, where m is the number of dimensions. f : ndarray an ndarray of function values used to train the polynomial approximation. The shape of `f` is M-by-1. v : ndarray, optional contains the regularization parameters that model error in the function values (default None) e : ndarray, optional an ndarray containing the eigenvalues from the active subspace analysis. If present, the radial basis uses it to determine the appropriate anisotropy in the length scales. (default None) Notes ----- The approximation uses an multivariate, squared exponential radial basis. If `e` is not None, then the radial basis is anisotropic with length scales determined by `e`. Otherwise, the basis is isotropic. The length scale parameters (i.e., the rbf shape parameters) are determined with a maximum likelihood heuristic inspired by techniques for fitting a Gaussian process model. The approximation also includes a monomial basis with monomials of total degree up to order `N`. These are fit with weighted least-squares, where the weight matrix is the inverse of the matrix of radial basis functions evaluated at the training points. This method sets all the attributes of the class for use in the `predict` method. """ X, f, M, m = process_inputs_outputs(X, f) # check that there are enough points to train the polynomial if M < comb(self.N + m, m): raise Exception( 'Not enough points to fit response surface of order {:d}'. format(self.N)) # use maximum likelihood to tune parameters log10g = fminbound(_rbf_objective, -10.0, 1.0, args=( X, f, v, self.N, e, )) g = 10**(log10g) if e is None: ell = g * np.ones((m, 1)) if v is None: v = 1e-6 * np.ones(f.shape) else: ell = g * np.sum(e) / e[:m] if v is None: v = g * np.sum(e[m:]) * np.ones(f.shape) # ensure conditioning v = np.amax([v.reshape(f.shape), 1e-6 * np.ones(f.shape)], axis=0) # covariance matrix of observations K = exponential_squared(X, X, 1.0, ell) K += np.diag(v.reshape((M, ))) B = polynomial_bases(X, self.N)[0] p = B.shape[1] C = np.hstack((np.vstack((K, B.T)), np.vstack((B, np.zeros((p, p)))))) weights = np.linalg.solve(C, np.vstack((f, np.zeros((p, 1))))) radial_weights, poly_weights = weights[:M], weights[M:] res = f - np.dot(B, poly_weights) Rsqr = 1.0 - (np.dot(res.T, np.linalg.solve(K, res)) / np.dot(f.T, np.linalg.solve(K, f))) # store parameters self.X, self.f = X, f self.ell, self.K = ell, K self.Rsqr = Rsqr[0, 0] self.radial_weights, self.poly_weights = radial_weights, poly_weights