def plot_path(self, result: dict, group_size: List[int] = None): """plot the solution path""" if group_size is None: group_size = self.group_size lams = list(result.keys()) nz = compute_nonzeros(result[lams[-1]], group_size)[1] beta_norms = [] for lam in lams: beta_norms.append(self.compute_group_norm(result[lam][1:], group_size)) # lams = lams[1:] # beta_nonzero = [] # for i in range(len(beta_norms)): # beta_nonzero.append((np.array(beta_norms[i]) != 0).sum()) # first_nonzero = beta_nonzero.index([i for i in beta_nonzero if i > 0][0]) # if first_nonzero > 5: # first_nonzero -= 5 # else: # first_nonzero = 0 # lams = lams[first_nonzero:] # beta_norms = beta_norms[first_nonzero:] beta_norms = np.array(beta_norms) print(beta_norms.shape) plt.figure(figsize=(20, 5)) plt.plot(lams, beta_norms[:, nz]) plt.xlabel('Lambda') plt.ylabel('L2 norm of coefficients') plt.title('Group lasso path') plt.axis('tight') plt.legend(['beta' + str(i) for i in nz]) plt.show()
def fit_2(self, x: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch.Tensor], num_lams: int, max_iters: int = 1000, an: Union[int, float] = None, smooth: Union[float, int] = 0): """fit group lasso then followed by adaptive group lasso, saves time for basis expansion""" x = numpy_to_torch(x) y = numpy_to_torch(y) x = remove_intercept(x) x = self.normalize(x) x_basis = self.basis_expansion_(x, self.df, self.degree) group_size = [self.df] * x.shape[1] x_basis, group_size = add_intercept(x_basis, group_size) result = self.fit_path(x_basis, y, group_size, num_lams, max_iters, smooth=smooth) beta_gl = result[min(list(result.keys()))] weights = self.compute_weights(beta_gl) result = self.fit_path(x_basis, y, group_size, num_lams, max_iters, smooth=smooth, weights=weights) best_gic = np.inf best_lam = 0 best_beta = None if an is None: an = np.log(x.shape[1]) / x.shape[0] for lam in result.keys(): beta_full = result[lam] gic = self.compute_gic(x_basis, y, beta_full, an, group_size) print(f"lam:{lam}, gic:{gic}") if gic < best_gic: best_lam = lam best_beta = beta_full best_gic = gic self.beta_agl_gic = best_beta self.beta = best_beta num_nz, nz = compute_nonzeros(best_beta, group_size) print( f"The best lam {best_lam} and the best gic {best_gic}. Finally selected {num_nz - 1} nonzeros: {nz}" ) return self
def plot_functions(self, x: Union[np.ndarray, torch.Tensor], cols: int = 5): """plot the estimated functions""" x = numpy_to_torch(x) x = remove_intercept(x) x_n = self.normalize_test(x) x_basis = self.basis_expansion_(x_n, self.df, self.degree) beta = self.beta[1:] nz, nzs = compute_nonzeros(beta, [self.df] * x.shape[1]) nrows = nz // cols + 1 fig, ax = plt.subplots(nrows=nrows, ncols=cols, figsize=(20, 12)) x_o = torch.exp(x) - 0.1 for i, j in enumerate(nzs): eta = torch.matmul(x_basis[:, self.df * j:self.df * (j + 1)], beta[self.df * j:self.df * (j + 1)].double()) ax.flatten()[i].scatter(x_o.detach().numpy()[:, j], eta.detach().numpy()) ax.flatten()[i].title.set_text(f"Variable {j + 1}") plt.show()
def solution_path(self, x: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch.Tensor], num_lams: int, group_size: Union[int, List[int]], max_iters: int = 1000, smooth: Union[float, int] = 0, recompute_hg: bool = True, weight: List[Union[int, List[int]]] = None) \ -> (List[torch.Tensor], List[float]): """ fits the model with a use specified lambda :param x: the design matrix :param y: the response :param num_lams: number of lambdas :param group_size: list of group sizes, or simple group size if all groups are of the same size :param max_iters: the maximum number of iterations :param smooth: smoothness parameter :param recompute_hg: whether to recompute hg :param weight: feature weights :return: coefficients """ x = numpy_to_torch(x) y = numpy_to_torch(y) self.group_size = group_size if isinstance(group_size, int): group_size = [1] + [group_size] * (x.shape[1] // group_size) if weight is None: weight = [0] + [1] * len(group_size) weights = [np.sqrt(group_size[i]) * weight[i] for i in range(len(group_size))] assert np.sum(group_size) == x.shape[1], "Sum of group sizes do not match number of variables." betas = [] lam_max = self.find_max_lambda(x, y, weights[1:], group_size[1:]) lam_max *= (1 + 1 / num_lams * 10) lams = list(np.linspace(0, lam_max, num_lams)) lams.remove(0) lams.sort(reverse=True) lam_last = None for lam in lams: if not betas: # beta_full = self.solve(x, y, lam, group_size, max_iters, weights, smooth, recompute_hg) beta_full = torch.tensor([self.null_estimate(y)] + [0] * (sum(group_size) - 1)) betas.append(beta_full) lam_last = lam else: beta = betas[-1] strong_index = self.strong_rule(x, y, beta, group_size, lam, lam_last, weights) x_s, group_size_s, weight_s = self.strong_x(x, group_size, strong_index, weights) # start = datetime.now().timestamp() beta_s = self.solve(x_s, y, lam, group_size_s, max_iters, weight_s, smooth, recompute_hg, weight_multiplied=True) # end = datetime.now().timestamp() # print(f"solve {end - start}") beta_full = self.strong_to_full_beta(beta_s, group_size, strong_index) v = self.fail_kkt(x, y, beta_full, group_size, lam, strong_index, weights) while len(v) > 0: strong_index = list(set(strong_index + v)) x_s, group_size_s, weight_s = self.strong_x(x, group_size, strong_index, weights) beta_s = self.solve(x_s, y, lam, group_size_s, max_iters, weight_s, smooth, recompute_hg, weight_multiplied=True) beta_full = self.strong_to_full_beta(beta_s, group_size, strong_index) v = self.fail_kkt(x, y, beta_full, group_size, lam, strong_index, weights) betas.append(beta_full) lam_last = lam num_nz, nz = compute_nonzeros(beta_full, group_size) print(f"Fitted lam = {lam}, {num_nz - 1} nonzero variables {nz}") if sum([group_size[i] for i in nz]) > 2 * x.shape[0]: lams = lams[:lams.index(lam) + 1] break return betas, lams,
def compute_gic(self, x: torch.Tensor, y: torch.Tensor, beta: torch.Tensor, an: Union[int, float], group_size: List[int]): """computes the generalized information criterion""" likelihood = self.compute_like(x, y, beta) num_nonzero = compute_nonzeros(beta, group_size)[0] return -2 * likelihood.detach().item() + an * num_nonzero