slope 6.6.0
Loading...
Searching...
No Matches
hybrid.h
Go to the documentation of this file.
1
6
7#pragma once
8
9#include "../clusters.h"
10#include "../losses/loss.h"
11#include "../sorted_l1_norm.h"
12#include "hybrid_cd.h"
13#include "pgd.h"
14#include "solver.h"
15#include <memory>
16#include <optional>
17
18namespace slope {
19
36class Hybrid : public SolverBase
37{
38public:
51 bool intercept,
52 bool update_clusters,
53 int cd_iterations,
54 const std::string& cd_type,
55 std::optional<int> random_seed = std::nullopt)
57 , update_clusters(update_clusters)
58 , cd_iterations(cd_iterations)
59 , cd_type(cd_type)
60 , rng(random_seed.has_value() ? std::mt19937(*random_seed)
61 : std::mt19937(std::random_device{}()))
62 {
63 }
64
66 void run(
67 Eigen::VectorXd& beta0,
68 Eigen::VectorXd& beta,
69 Eigen::MatrixXd& eta,
70 const Eigen::ArrayXd& lambda,
71 const std::unique_ptr<Loss>& loss,
72 const SortedL1Norm& penalty,
73 const Eigen::VectorXd& gradient,
74 const std::vector<int>& working_set,
75 const Eigen::MatrixXd& x,
76 const Eigen::VectorXd& x_centers,
77 const Eigen::VectorXd& x_scales,
78 const Eigen::MatrixXd& y,
79 const Eigen::VectorXd& observation_weights = Eigen::VectorXd()) override;
80
82 void run(
83 Eigen::VectorXd& beta0,
84 Eigen::VectorXd& beta,
85 Eigen::MatrixXd& eta,
86 const Eigen::ArrayXd& lambda,
87 const std::unique_ptr<Loss>& loss,
88 const SortedL1Norm& penalty,
89 const Eigen::VectorXd& gradient,
90 const std::vector<int>& working_set,
91 const Eigen::SparseMatrix<double>& x,
92 const Eigen::VectorXd& x_centers,
93 const Eigen::VectorXd& x_scales,
94 const Eigen::MatrixXd& y,
95 const Eigen::VectorXd& observation_weights = Eigen::VectorXd()) override;
96
98 void run(
99 Eigen::VectorXd& beta0,
100 Eigen::VectorXd& beta,
101 Eigen::MatrixXd& eta,
102 const Eigen::ArrayXd& lambda,
103 const std::unique_ptr<Loss>& loss,
104 const SortedL1Norm& penalty,
105 const Eigen::VectorXd& gradient,
106 const std::vector<int>& working_set,
107 const Eigen::Map<Eigen::MatrixXd>& x,
108 const Eigen::VectorXd& x_centers,
109 const Eigen::VectorXd& x_scales,
110 const Eigen::MatrixXd& y,
111 const Eigen::VectorXd& observation_weights = Eigen::VectorXd()) override;
112
114 void run(
115 Eigen::VectorXd& beta0,
116 Eigen::VectorXd& beta,
117 Eigen::MatrixXd& eta,
118 const Eigen::ArrayXd& lambda,
119 const std::unique_ptr<Loss>& loss,
120 const SortedL1Norm& penalty,
121 const Eigen::VectorXd& gradient,
122 const std::vector<int>& working_set,
123 const Eigen::Map<Eigen::SparseMatrix<double>>& x,
124 const Eigen::VectorXd& x_centers,
125 const Eigen::VectorXd& x_scales,
126 const Eigen::MatrixXd& y,
127 const Eigen::VectorXd& observation_weights = Eigen::VectorXd()) override;
128
129private:
144 template<typename MatrixType>
145 void runImpl(Eigen::VectorXd& beta0,
146 Eigen::VectorXd& beta,
147 Eigen::MatrixXd& eta,
148 const Eigen::ArrayXd& lambda,
149 const std::unique_ptr<Loss>& loss,
150 const SortedL1Norm& penalty,
151 const Eigen::VectorXd& gradient_in,
152 const std::vector<int>& working_set,
153 const MatrixType& x,
154 const Eigen::VectorXd& x_centers,
155 const Eigen::VectorXd& x_scales,
156 const Eigen::MatrixXd& y,
157 const Eigen::VectorXd& observation_weights)
158 {
159 using Eigen::MatrixXd;
160 using Eigen::VectorXd;
161
162 const int n = x.rows();
163 const int m = eta.cols();
164 const VectorXd weights =
165 detail::normalizeObservationWeights(observation_weights, n);
166
167 PGD pgd_solver(jit_normalization, intercept, "pgd");
168
169 // Run proximal gradient descent
170 pgd_solver.run(beta0,
171 beta,
172 eta,
173 lambda,
174 loss,
175 penalty,
176 gradient_in,
177 working_set,
178 x,
179 x_centers,
180 x_scales,
181 y,
182 weights);
183
184 Clusters clusters(beta);
185
186 // TODO: Make these parameters and initialize once
187 MatrixXd w = MatrixXd::Ones(n, m);
188 MatrixXd z = y;
189
190 loss->updateWeightsAndWorkingResponse(w, z, eta, y, weights);
191
192 MatrixXd residual = eta - z;
193 detail::maskZeroWeightRows(residual, weights);
194 const VectorXd weight_sums = w.colwise().sum().transpose();
195
196 Eigen::ArrayXd lambda_cumsum(lambda.size() + 1);
197 lambda_cumsum(0) = 0.0;
198 std::partial_sum(lambda.begin(), lambda.end(), lambda_cumsum.begin() + 1);
199
200 for (int it = 0; it < this->cd_iterations; ++it) {
201 double old_obj =
202 computeObjective(penalty, beta, residual, w, lambda, working_set);
203
204 // Store old values to revert if no progress is made
205 Clusters old_clusters = clusters;
206 Eigen::MatrixXd old_residual = residual;
207 Eigen::VectorXd old_beta = beta;
208 Eigen::VectorXd old_beta0 = beta0;
209
210 coordinateDescent(beta0,
211 beta,
212 residual,
213 clusters,
214 lambda_cumsum,
215 x,
216 w,
217 weight_sums,
218 x_centers,
219 x_scales,
220 this->intercept,
221 this->jit_normalization,
222 this->update_clusters,
223 rng,
224 this->cd_type);
225
226 detail::maskZeroWeightRows(residual, weights);
227
228 double new_obj =
229 computeObjective(penalty, beta, residual, w, lambda, working_set);
230
231 if (!std::isfinite(new_obj) || new_obj > old_obj) {
232 // No progress, revert to previous state
233 clusters = old_clusters;
234 residual = old_residual;
235 beta = old_beta;
236 beta0 = old_beta0;
237
238 break;
239 }
240 }
241
242 // The residual is kept up to date, but not eta. So we need to compute
243 // it here.
244 eta = residual + z;
245 // TODO: register convergence status
246 }
247
248 double computeObjective(const SortedL1Norm& penalty,
249 const Eigen::VectorXd& beta,
250 const Eigen::MatrixXd& residual,
251 const Eigen::MatrixXd& w,
252 const Eigen::ArrayXd& lambda,
253 const std::vector<int>& working_set)
254 {
255 double val =
256 0.5 * (residual.array().square() * w.array()).sum() / residual.rows() +
257 penalty.eval(beta(working_set), lambda.head(working_set.size()));
258
259 return val;
260 }
261
262 // TODO: These should be used in the PGD solver and taken as arguments to the
263 // Hybrid solver and not just set and ignored here.
264 double pgd_learning_rate =
265 1.0;
266 double pgd_learning_rate_decr =
267 0.5;
268
269 bool update_clusters = false;
270 int cd_iterations = 10;
271 std::string cd_type =
272 "cyclical";
273 std::mt19937 rng{
274 std::random_device{}()
275 };
276};
277
278} // namespace slope
Representation of the nonzero clusters in SLOPE.
Definition clusters.h:23
void run(Eigen::VectorXd &beta0, Eigen::VectorXd &beta, Eigen::MatrixXd &eta, const Eigen::ArrayXd &lambda, const std::unique_ptr< Loss > &loss, const SortedL1Norm &penalty, const Eigen::VectorXd &gradient, const std::vector< int > &working_set, const Eigen::SparseMatrix< double > &x, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const Eigen::MatrixXd &y, const Eigen::VectorXd &observation_weights=Eigen::VectorXd()) override
Pure virtual function defining the solver's optimization routine.
void run(Eigen::VectorXd &beta0, Eigen::VectorXd &beta, Eigen::MatrixXd &eta, const Eigen::ArrayXd &lambda, const std::unique_ptr< Loss > &loss, const SortedL1Norm &penalty, const Eigen::VectorXd &gradient, const std::vector< int > &working_set, const Eigen::MatrixXd &x, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const Eigen::MatrixXd &y, const Eigen::VectorXd &observation_weights=Eigen::VectorXd()) override
Pure virtual function defining the solver's optimization routine.
void run(Eigen::VectorXd &beta0, Eigen::VectorXd &beta, Eigen::MatrixXd &eta, const Eigen::ArrayXd &lambda, const std::unique_ptr< Loss > &loss, const SortedL1Norm &penalty, const Eigen::VectorXd &gradient, const std::vector< int > &working_set, const Eigen::Map< Eigen::SparseMatrix< double > > &x, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const Eigen::MatrixXd &y, const Eigen::VectorXd &observation_weights=Eigen::VectorXd()) override
Pure virtual function defining the solver's optimization routine.
Hybrid(JitNormalization jit_normalization, bool intercept, bool update_clusters, int cd_iterations, const std::string &cd_type, std::optional< int > random_seed=std::nullopt)
Constructs Hybrid solver for SLOPE optimization.
Definition hybrid.h:50
void run(Eigen::VectorXd &beta0, Eigen::VectorXd &beta, Eigen::MatrixXd &eta, const Eigen::ArrayXd &lambda, const std::unique_ptr< Loss > &loss, const SortedL1Norm &penalty, const Eigen::VectorXd &gradient, const std::vector< int > &working_set, const Eigen::Map< Eigen::MatrixXd > &x, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const Eigen::MatrixXd &y, const Eigen::VectorXd &observation_weights=Eigen::VectorXd()) override
Pure virtual function defining the solver's optimization routine.
Proximal Gradient Descent solver for SLOPE optimization.
Definition pgd.h:30
JitNormalization jit_normalization
JIT feature normalization strategy.
Definition solver.h:173
bool intercept
If true, fits intercept term.
Definition solver.h:174
SolverBase(JitNormalization jit_normalization, bool intercept)
Constructs a base solver for SLOPE optimization.
Definition solver.h:39
Class representing the Sorted L1 Norm.
The declaration of the Clusters class.
An implementation of the coordinate descent step in the hybrid algorithm for solving SLOPE.
The declartion of the Objctive class and its subclasses, which represent the data-fitting part of the...
Namespace containing SLOPE regression implementation.
Definition clusters.h:11
double coordinateDescent(Eigen::VectorXd &beta0, Eigen::VectorXd &beta, Eigen::MatrixXd &residual, Clusters &clusters, const Eigen::ArrayXd &lambda_cumsum, const T &x, const Eigen::MatrixXd &w, const Eigen::VectorXd &weight_sums, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const bool intercept, const JitNormalization jit_normalization, const bool update_clusters, std::mt19937 &rng, const std::string &cd_type="cyclical")
Definition hybrid_cd.h:601
JitNormalization
Enums to control predictor standardization behavior.
Proximal Gradient Descent solver implementation for SLOPE.
Numerical solver class for SLOPE (Sorted L-One Penalized Estimation).
The declaration of the SortedL1Norm class.