@@ -55,12 +55,15 @@ def fit(self, X, y, sample_weights):
5555 residual = y - (X @ self .coef_ + self .intercept_ )
5656
5757 iteration = 0
58- max_change = np .inf
5958
6059 # 2. Coordinate Descent Loop
6160 while iteration < self .max_iter and not all (self .coef_ == 0 ):
61+ max_change = 0
62+ max_abs_coef = 0.0
63+
6264 # Sort features by instability (highest CV first)
63- for j in np .argsort (cv )[::- 1 ]:
65+ indices = np .argsort (cv )[::- 1 ]
66+ for j in indices :
6467 old_coef = self .coef_ [j ]
6568
6669 if old_coef == 0 :
@@ -91,15 +94,57 @@ def fit(self, X, y, sample_weights):
9194 self .intercept_ = weights .mean (axis = 0 )[- 1 ]
9295 residual = y - (X @ self .coef_ + self .intercept_ )
9396 iteration = 0
97+ max_change = np .inf
9498 break
9599
96100 residual -= (new_coef - old_coef ) * X [:, j ]
97- change = abs (new_coef - old_coef ) / abs (old_coef )
101+ change = abs (new_coef - old_coef )
102+ if change > max_change :
103+ max_change = change
104+ # change = abs(new_coef - old_coef) / abs(old_coef)
98105 # change = abs(self.intercept_ - old_intercept) / abs(old_intercept)
99- max_change = max (max_change , change )
106+ # max_change = max(max_change, change)
107+
108+ max_abs_coef = np .max (np .abs (self .coef_ ))
109+
110+ # Критерий 1: max_j |w_new - w_old| <= tol * max_j |w_j|
111+ if max_change <= self .tol * max_abs_coef :
112+ # Критерий 2: Dual Gap <= tol * ||y||^2 / n_samples
113+ # Вычисляем компоненты дуального зазора
114+ # Примечание: Для Lasso с весами lambda_j = threshold_j
115+
116+ # 1. Вычисляем корреляции признаков с остатками
117+ xt_residual = X .T @ residual
118+ y_sq_sum = np .sum ((y - self .intercept_ ) ** 2 )
119+
120+ # 2. Масштабирующий фактор для обеспечения дуальной допустимости
121+ # В sklearn: dual_scale = min(1, alpha / max(|X.T @ res|))
122+ # Здесь используем ваши индивидуальные threshold_j
123+ dual_norm = 0
124+ for j in range (self .n_features ):
125+ if cv [j ] * y_sq_sum > 0 :
126+ dual_norm = max (dual_norm , abs (xt_residual [j ]) / cv [j ] * y_sq_sum )
127+
128+ if dual_norm > 1.0 :
129+ const_residual = residual / dual_norm
130+ else :
131+ const_residual = residual
132+
133+ # 3. Вычисление Gap: Primal Objective - Dual Objective
134+ # Primal = 0.5 * ||res||^2 + sum(threshold_j * |w_j|)
135+ # Dual = 0.5 * ||y-intercept||^2 - 0.5 * ||y-intercept - const_residual||^2
136+ primal_obj = 0.5 * np .sum (residual ** 2 ) + np .sum (cv * y_sq_sum * np .abs (self .coef_ ))
137+ dual_obj = 0.5 * y_sq_sum - 0.5 * np .sum ((y - self .intercept_ - const_residual ) ** 2 )
138+
139+ dual_gap = primal_obj - dual_obj
140+
141+ # Итоговая проверка по формуле со скрина
142+ if dual_gap <= self .tol * (y_sq_sum / self .n_samples ):
143+ break
144+
145+ # if max_change < self.tol:
146+ # break
100147
101- if max_change < self .tol :
102- break
103148 iteration += 1
104149 # print(iteration)
105150 return self
0 commit comments