diff --git a/epde/globals.py b/epde/globals.py index 4de146c5..33d03fc5 100644 --- a/epde/globals.py +++ b/epde/globals.py @@ -104,7 +104,7 @@ class VerboseManager: def init_verbose(plot_DE_solutions : bool = False, show_iter_idx : bool = True, show_iter_fitness : bool = False, show_iter_stats : bool = False, show_ann_loss : bool = False, show_warnings : bool = False, - candidate_objectives : bool = False): + candidate_objectives : bool = True): """ Method for initialized of manager for output in text form diff --git a/epde/operators/common/sparsity.py b/epde/operators/common/sparsity.py index 37f0e887..9bb51799 100644 --- a/epde/operators/common/sparsity.py +++ b/epde/operators/common/sparsity.py @@ -55,12 +55,15 @@ def fit(self, X, y, sample_weights): residual = y - (X @ self.coef_ + self.intercept_) iteration = 0 - max_change = np.inf # 2. Coordinate Descent Loop while iteration < self.max_iter and not all(self.coef_ == 0): + max_change = 0 + max_abs_coef = 0.0 + # Sort features by instability (highest CV first) - for j in np.argsort(cv)[::-1]: + indices = np.argsort(cv)[::-1] + for j in indices: old_coef = self.coef_[j] if old_coef == 0: @@ -91,15 +94,57 @@ def fit(self, X, y, sample_weights): self.intercept_ = weights.mean(axis=0)[-1] residual = y - (X @ self.coef_ + self.intercept_) iteration = 0 + max_change = np.inf break residual -= (new_coef - old_coef) * X[:, j] - change = abs(new_coef - old_coef) / abs(old_coef) + change = abs(new_coef - old_coef) + if change > max_change: + max_change = change + # change = abs(new_coef - old_coef) / abs(old_coef) # change = abs(self.intercept_ - old_intercept) / abs(old_intercept) - max_change = max(max_change, change) + # max_change = max(max_change, change) + + max_abs_coef = np.max(np.abs(self.coef_)) + + # Критерий 1: max_j |w_new - w_old| <= tol * max_j |w_j| + if max_change <= self.tol * max_abs_coef: + # Критерий 2: Dual Gap <= tol * ||y||^2 / n_samples + # Вычисляем компоненты дуального зазора + # Примечание: Для Lasso с весами lambda_j = threshold_j + + # 1. Вычисляем корреляции признаков с остатками + xt_residual = X.T @ residual + y_sq_sum = np.sum((y - self.intercept_) ** 2) + + # 2. Масштабирующий фактор для обеспечения дуальной допустимости + # В sklearn: dual_scale = min(1, alpha / max(|X.T @ res|)) + # Здесь используем ваши индивидуальные threshold_j + dual_norm = 0 + for j in range(self.n_features): + if cv[j] * y_sq_sum > 0: + dual_norm = max(dual_norm, abs(xt_residual[j]) / cv[j] * y_sq_sum) + + if dual_norm > 1.0: + const_residual = residual / dual_norm + else: + const_residual = residual + + # 3. Вычисление Gap: Primal Objective - Dual Objective + # Primal = 0.5 * ||res||^2 + sum(threshold_j * |w_j|) + # Dual = 0.5 * ||y-intercept||^2 - 0.5 * ||y-intercept - const_residual||^2 + primal_obj = 0.5 * np.sum(residual ** 2) + np.sum(cv * y_sq_sum * np.abs(self.coef_)) + dual_obj = 0.5 * y_sq_sum - 0.5 * np.sum((y - self.intercept_ - const_residual) ** 2) + + dual_gap = primal_obj - dual_obj + + # Итоговая проверка по формуле со скрина + if dual_gap <= self.tol * (y_sq_sum / self.n_samples): + break + + # if max_change < self.tol: + # break - if max_change < self.tol: - break iteration += 1 # print(iteration) return self