Skip to content

Commit a2cbadc

Browse files
authored
Merge pull request #13 from Leona-LYT/main
modified tiny error in _loss.py and add a path solution file
2 parents 0b740d5 + 19a79ae commit a2cbadc

File tree

2 files changed

+220
-2
lines changed

2 files changed

+220
-2
lines changed

rehline/_loss.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def __call__(self, x):
6868
----------
6969
x: {array-like} of shape (n_samples, )
7070
Training vector, where `n_samples` is the number of samples
71+
72+
For ERM question, the input of this function is np.dot(X, self.coef_) rather than a single X.
7173
"""
7274
if (self.L > 0) and (self.H > 0):
7375
assert self.relu_coef.shape[1] == self.rehu_coef.shape[1], "n_samples for `relu_coef` and `rehu_coef` should be the same shape!"
@@ -80,9 +82,9 @@ def __call__(self, x):
8082
ans = 0
8183
if len(self.relu_coef) > 0:
8284
relu_input = (self.relu_coef.T * x[:,np.newaxis]).T + self.relu_intercept
83-
ans += np.sum(relu(relu_input), 0).sum()
85+
ans += np.sum(_relu(relu_input), 0).sum()
8486
if len(self.rehu_coef) > 0:
8587
rehu_input = (self.rehu_coef.T * x[:,np.newaxis]).T + self.rehu_intercept
86-
ans += np.sum(rehu(rehu_input, cut=self.rehu_cut), 0).sum()
88+
ans += np.sum(_rehu(rehu_input, cut=self.rehu_cut), 0).sum()
8789

8890
return ans

rehline/_path_sol.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import numpy as np
2+
import time
3+
import matplotlib.pyplot as plt
4+
from rehline import plqERM_Ridge
5+
from rehline import _make_loss_rehline_param
6+
from ._loss import ReHLoss
7+
8+
9+
def plqERM_Ridge_path_sol(
10+
X,
11+
y,
12+
*,
13+
loss,
14+
constraint=[ ],
15+
eps=1e-3,
16+
n_Cs=100,
17+
Cs=None,
18+
max_iter=5000,
19+
tol=1e-4,
20+
verbose=0,
21+
shrink=1,
22+
warm_start=False,
23+
return_time=True,
24+
plot_path=False
25+
):
26+
"""
27+
Compute the PLQ Empirical Risk Minimization (ERM) path over a range of regularization parameters.
28+
This function evaluates the model's performance for different values of the regularization parameter
29+
and provides structured benchmarking output.
30+
31+
Parameters
32+
----------
33+
X : ndarray of shape (n_samples, n_features)
34+
Training input samples.
35+
36+
y : ndarray of shape (n_samples,)
37+
Target values corresponding to each input sample.
38+
39+
loss : dict
40+
Dictionary describing the PLQ loss function parameters. Used to construct the loss object internally.
41+
42+
constraint : list of dict, optional (default=[])
43+
List of constraints applied to the optimization problem. Each constraint should be represented
44+
as a dictionary compatible with the solver.
45+
46+
eps : float, default=1e-3
47+
Defines the range of regularization values when `Cs` is not provided. Specifically, the smallest
48+
regularization value will be approximately `eps` times the largest.
49+
50+
n_Cs : int, default=100
51+
Number of regularization values to evaluate if `Cs` is not provided.
52+
53+
Cs : array-like of shape (n_Cs,), optional
54+
Explicit values of regularization strength `C` to use. If `None`, the values are generated
55+
logarithmically between 1e-2 and 1e3.
56+
57+
max_iter : int, default=5000
58+
Maximum number of iterations allowed for the optimization solver at each `C`.
59+
60+
tol : float, default=1e-4
61+
Tolerance for solver convergence.
62+
63+
verbose : int, default=0
64+
Controls verbosity level of output. Set to higher values (e.g., 1 or 2) for detailed progress logs.
65+
66+
shrink : float, default=1
67+
Shrinkage factor for the solver, potentially influencing convergence behavior.
68+
69+
warm_start : bool, default=False
70+
If True, reuse the previous solution to warm-start the next solver step, speeding up convergence.
71+
72+
return_time : bool, default=True
73+
If True, return timing information for each value of `C`.
74+
75+
plot_path : bool, default=False
76+
If True, generate a plot of the coefficient paths as a function of `C`.
77+
78+
Returns
79+
-------
80+
Cs : ndarray of shape (n_Cs,)
81+
Array of regularization parameters used in the path.
82+
83+
times : list of float
84+
Time in seconds taken to fit the model at each `C`. Returned only if `return_time=True`.
85+
86+
n_iters : list of int
87+
Number of iterations used by the solver at each regularization value.
88+
89+
loss_values : list of float
90+
Final loss values (including regularization term) at each `C`.
91+
92+
L2_norms : list of float
93+
L2 norm of the coefficients (excluding bias) at each `C`.
94+
95+
coefs : ndarray of shape (n_features, n_Cs)
96+
Learned model coefficients at each regularization strength.
97+
98+
Example
99+
-------
100+
101+
>>> # generate data
102+
>>> np.random.seed(42)
103+
>>> n, d, C = 1000, 5, 0.5
104+
>>> X = np.random.randn(n, d)
105+
>>> beta0 = np.random.randn(d)
106+
>>> y = np.sign(X.dot(beta0) + np.random.randn(n))
107+
>>> # define loss function
108+
>>> loss = {'name': 'svm'}
109+
>>> Cs = [2000, 3000, 4000]
110+
>>> constrain = [{'name': 'none'}]
111+
112+
113+
>>> # calculate
114+
>>> Cs, times, n_iters, losses, norms, coefs = plqERM_path_sol(
115+
... X, y, loss=loss, Cs=Cs, max_iter=100000,tol=1e-4,verbose=1,
116+
... warm_start=False, constrain=constrain, return_time=True, plot_path=True
117+
... )
118+
119+
"""
120+
121+
n_samples, n_features = X.shape
122+
123+
if Cs is None:
124+
Cs = np.logspace(-2, 3, n_Cs)
125+
126+
# Sort Cs to ensure computation starts from the smallest value
127+
Cs = np.sort(Cs)
128+
n_Cs = len(Cs)
129+
coefs = np.zeros((n_features, n_Cs))
130+
n_iters = []
131+
times = []
132+
loss_values = []
133+
L2_norms = []
134+
135+
136+
if return_time:
137+
total_start = time.time()
138+
139+
U, V, Tau, S, T = _make_loss_rehline_param(loss, X, y)
140+
loss_obj = ReHLoss(U, V, S, T, Tau)
141+
142+
for i, C in enumerate(Cs):
143+
if return_time:
144+
start_time = time.time()
145+
146+
clf = plqERM_Ridge(
147+
loss=loss, constraint=constraint, C=C,
148+
max_iter=max_iter, tol=tol, shrink=shrink, verbose=verbose,
149+
warm_start=warm_start
150+
)
151+
152+
if warm_start and (i>0):
153+
clf.Lambda = Lambda
154+
clf.Gamma = Gamma
155+
clf.xi = xi
156+
157+
clf.fit(X, y)
158+
coefs[:, i] = clf.coef_
159+
160+
# Compute loss function parameters for ReHLoss
161+
l2_norm = 0.5 * np.linalg.norm(clf.coef_) ** 2
162+
score = clf.decision_function(X)
163+
total_loss = loss_obj(score) + l2_norm
164+
loss_values.append(round(total_loss, 4))
165+
L2_norms.append(round(np.linalg.norm(clf.coef_), 4))
166+
167+
if warm_start:
168+
Lambda = clf.Lambda
169+
Gamma = clf.Gamma
170+
xi = clf.xi
171+
172+
if return_time:
173+
elapsed_time = time.time() - start_time
174+
times.append(elapsed_time)
175+
176+
n_iters.append(clf.n_iter_)
177+
178+
if return_time:
179+
total_time = time.time() - total_start
180+
avg_time_per_iter = total_time / sum(n_iters) if sum(n_iters) > 0 else float("inf")
181+
182+
183+
if verbose:
184+
print("\nPLQ ERM Path Solution Results")
185+
print("=" * 90)
186+
print(f"{'C Value':<15}{'Iterations':<15}{'Time (s)':<20}{'Loss':<20}{'L2 Norm':<20}")
187+
print("-" * 90)
188+
189+
for C, iters, t, loss_val, l2 in zip(Cs, n_iters, times, loss_values, L2_norms):
190+
if return_time:
191+
print(f"{C:<15.4g}{iters:<15}{t:<20.6f}{loss_val:<20.6f}{l2:<20.6f}")
192+
else:
193+
print(f"{C:<15.4g}{iters:<15}{loss_val:<20.6f}{l2:<20.6f}")
194+
195+
print("=" * 90)
196+
print(f"{'Total Time':<12}{total_time:.6f} sec")
197+
print(f"{'Avg Time/Iter':<12}{avg_time_per_iter:.6f} sec")
198+
print("=" * 90)
199+
200+
if plot_path:
201+
import matplotlib.pyplot as plt
202+
plt.figure(figsize=(10, 6))
203+
for i in range(n_features):
204+
plt.plot(Cs, coefs[i, :], label=f'Feature {i+1}')
205+
plt.xscale('log')
206+
plt.xlabel('C')
207+
plt.ylabel('Coefficient Value')
208+
plt.title('Regularization Path')
209+
plt.legend()
210+
plt.show()
211+
212+
if return_time:
213+
return Cs, times, n_iters, loss_values, L2_norms, coefs
214+
else:
215+
return Cs, n_iters, loss_values, L2_norms, coefs
216+

0 commit comments

Comments
 (0)