slope 6.5.4
Loading...
Searching...
No Matches
cv.h
Go to the documentation of this file.
1
11#pragma once
12
13#include "eigen_compat.h"
14#include "folds.h"
15#include "score.h"
16#include "slope.h"
17#include <atomic>
18#include <vector>
19
20#ifdef _OPENMP
21#include <omp.h>
22#endif
23
24namespace slope {
25
26using slope::all;
27
35{
39 Eigen::MatrixXd score;
40
42 std::map<std::string, double> params;
43
45 Eigen::ArrayXd alphas;
46
48 Eigen::ArrayXd mean_scores;
49
52 Eigen::ArrayXd std_errors;
53};
54
63{
66 std::vector<GridResult> results;
67
70 std::map<std::string, double> best_params;
71
73 double best_score;
74
77
81};
82
91{
93 int n_folds = 10;
94
96 int n_repeats = 1;
97
99 std::string metric = "mse";
100
102 uint64_t random_seed = 42;
103
105 bool copy_x = true;
106
108 std::map<std::string, std::vector<double>> hyperparams;
109
111 std::map<std::string, std::vector<double>> default_hyperparams = {
112 { "q", { 0.1 } },
113 { "gamma", { 0.0 } },
114 };
115
117 std::optional<std::vector<std::vector<std::vector<int>>>> predefined_folds;
118};
119
120namespace detail {
121
122std::vector<std::map<std::string, double>>
123createGrid(const std::map<std::string, std::vector<double>>& param_values);
124
125void
126findBestParameters(CvResult& cv_result, const std::unique_ptr<Score>& scorer);
127
128template<typename T>
129Eigen::ArrayXd
130fitToFold(Eigen::MatrixBase<T>& x,
131 const Eigen::MatrixXd& y,
132 const Folds& folds,
133 const std::unique_ptr<Loss>& loss,
134 const std::unique_ptr<Score>& scorer,
135 const Eigen::ArrayXd& alphas,
136 Slope& thread_model,
137 const int fold,
138 const int rep,
139 const double gamma = 0.0,
140 const bool copy_x = true)
141{
142 Eigen::ArrayXd scores = Eigen::ArrayXd::Zero(alphas.size());
143
144 if (copy_x) {
145 thread_model.setModifyX(true);
146
147 auto [x_train, y_train, x_test, y_test] = folds.split(x, y, fold, rep);
148
149 auto path = thread_model.path(x_train, y_train, alphas);
150
151 if (gamma > 0) {
152 path = thread_model.relax(path, x_train, y_train, gamma);
153 }
154
155 for (int j = 0; j < path.size(); ++j) {
156 auto eta = path(j).predict(x_test, "linear");
157 scores(j) = scorer->eval(eta, y_test, loss);
158 }
159
160 } else {
161 thread_model.setModifyX(false);
162
163 auto train_idx = folds.getTrainingIndices(fold, rep);
164 auto test_idx = folds.getTestIndices(fold, rep);
165
166 // Create views
167 auto x_train = x(train_idx, all);
168 auto x_test = x(test_idx, all);
169
170 Eigen::MatrixXd y_train = y(train_idx, all);
171 Eigen::MatrixXd y_test = y(test_idx, all);
172
173 auto path = thread_model.path(x_train, y_train, alphas);
174
175 if (gamma > 0) {
176 path = thread_model.relax(path, x_train, y_train, gamma);
177 }
178
179 for (int j = 0; j < path.size(); ++j) {
180 auto eta = path(j).predict(x_test, "linear");
181 scores(j) = scorer->eval(eta, y_test, loss);
182 }
183 }
184
185 return scores;
186}
187
188template<typename T>
189Eigen::ArrayXd
190fitToFold(Eigen::SparseMatrixBase<T>& x,
191 const Eigen::MatrixXd& y,
192 const Folds& folds,
193 const std::unique_ptr<Loss>& loss,
194 const std::unique_ptr<Score>& scorer,
195 const Eigen::ArrayXd& alphas,
196 Slope& thread_model,
197 const int fold,
198 const int rep,
199 const double gamma = 0.0,
200 const bool copy_x = true)
201{
202 thread_model.setModifyX(true);
203
204 auto [x_train, y_train, x_test, y_test] = folds.split(x, y, fold, rep);
205
206 auto path = thread_model.path(x_train, y_train, alphas);
207
208 if (gamma > 0) {
209 path = thread_model.relax(path, x_train, y_train, gamma);
210 }
211
212 Eigen::ArrayXd scores = Eigen::ArrayXd::Zero(path.size());
213
214 for (int j = 0; j < path.size(); ++j) {
215 auto eta = path(j).predict(x_test, "linear");
216 scores(j) = scorer->eval(eta, y_test, loss);
217 }
218
219 return scores;
220}
221
222} // namespace detail
223
247template<typename T>
248CvResult
250 Eigen::EigenBase<T>& x,
251 const Eigen::MatrixXd& y_in,
252 const CvConfig& config = CvConfig())
253{
254 CvResult cv_result;
255
256 int n = y_in.rows();
257
258 auto loss = setupLoss(model.getLossType());
259
260 auto y = loss->preprocessResponse(y_in);
261 auto scorer = Score::create(config.metric);
262
263 auto hyperparams = config.default_hyperparams;
264
265 // Override with user-specified parameters
266 for (const auto& [key, values] : config.hyperparams) {
267 hyperparams[key] = values;
268 }
269
270 auto grid = detail::createGrid(hyperparams);
271
272 // Total number of evaluations (n_repeats * n_folds)
273 Folds folds =
274 config.predefined_folds.has_value()
275 ? Folds(*config.predefined_folds)
276 : Folds(n, config.n_folds, config.n_repeats, config.random_seed);
277
278 int n_evals = folds.numEvals();
279
280 for (const auto& params : grid) {
281 GridResult result;
282 result.params = params;
283
284 double q = params.at("q");
285 double gamma = params.at("gamma");
286
287 model.setQ(q);
288
289 auto initial_path = model.path(x, y);
290
291 result.alphas = initial_path.getAlpha();
292 int n_alpha = result.alphas.size();
293
294 assert((result.alphas > 0).all());
295
296 Eigen::MatrixXd scores = Eigen::MatrixXd::Zero(n_evals, n_alpha);
297
298#ifdef _OPENMP
299 Eigen::setNbThreads(1);
300#endif
301
302 // Thread-safety for exceptions
303 std::vector<std::string> thread_errors(n_evals);
304 std::atomic<bool> had_exception{ false };
305
306#ifdef _OPENMP
307 // omp_set_max_active_levels() is OpenMP 3.0; MSVC's vcomp runtime only
308 // implements 2.0, where omp_set_nested() is the way to stop nesting.
309#if _OPENMP >= 200805
310 omp_set_max_active_levels(1);
311#else
312 omp_set_nested(0);
313#endif
314#pragma omp parallel for num_threads(Threads::get()) \
315 shared(scores, thread_errors, had_exception)
316#endif
317 for (int i = 0; i < n_evals; ++i) {
318 try {
319 auto [rep, fold] = std::div(i, folds.numFolds());
320
321 Slope thread_model = model;
322
323 scores.row(i) = detail::fitToFold(x.derived(),
324 y,
325 folds,
326 loss,
327 scorer,
328 result.alphas,
329 thread_model,
330 fold,
331 rep,
332 gamma,
333 config.copy_x);
334
335 } catch (const std::exception& e) {
336 thread_errors[i] = e.what();
337 had_exception = true;
338 } catch (...) {
339 thread_errors[i] = "Unknown exception";
340 had_exception = true;
341 }
342 }
343
344 if (had_exception) {
345 std::string error_message = "Exception(s) during cross-validation:\n";
346 for (int i = 0; i < n_evals; ++i) {
347 if (!thread_errors[i].empty()) {
348 error_message +=
349 "Fold " + std::to_string(i) + ": " + thread_errors[i] + "\n";
350 }
351 }
352 throw std::runtime_error(error_message);
353 }
354
355 result.mean_scores = scores.colwise().mean();
356 result.std_errors = stdDevs(scores).array() / std::sqrt(n_evals);
357 result.score = std::move(scores);
358 cv_result.results.push_back(result);
359 }
360
361#ifdef _OPENMP
362 Eigen::setNbThreads(0);
363#endif
364
365 detail::findBestParameters(cv_result, scorer);
366
367 return cv_result;
368}
369
370} // namespace slope
Manages data partitioning for cross-validation.
Definition folds.h:29
size_t numEvals() const
Get the total number of folds (repetitions * folds)
Definition folds.h:166
size_t numFolds() const
Get the number of folds.
Definition folds.h:152
std::vector< int > getTrainingIndices(size_t fold_idx, size_t rep_idx=0) const
Get training indices for a specific fold and repetition.
const std::vector< int > & getTestIndices(size_t fold_idx, size_t rep_idx=0) const
Get test indices for a specific fold and repetition.
auto split(Eigen::EigenBase< T > &x, const Eigen::MatrixXd &y, size_t fold_idx, size_t rep_idx=0) const
Split data into training and test sets for a specific fold and repetition.
Definition folds.h:130
static std::unique_ptr< Score > create(const std::string &metric)
Eigen::MatrixXd predict(Eigen::EigenBase< T > &x, const std::string &type="response") const
Predict the response for a given input matrix.
Definition slope_fit.h:237
The SLOPE model.
Definition slope.h:57
SlopePath path(Eigen::EigenBase< T > &x, const Eigen::MatrixXd &y_in, Eigen::ArrayXd alpha=Eigen::ArrayXd::Zero(0), Eigen::ArrayXd lambda=Eigen::ArrayXd::Zero(0), std::function< bool()> check_interrupt=defaultInterruptChecker)
Computes SLOPE regression solution path for multiple alpha and lambda values.
Definition slope.h:393
const std::string & getLossType()
Get currently defined loss type.
SlopeFit relax(const SlopeFit &fit, T &x, const Eigen::VectorXd &y_in, const double gamma=0.0, Eigen::VectorXd beta0=Eigen::VectorXd(0), Eigen::VectorXd beta=Eigen::VectorXd(0))
Relaxes a fitted SLOPE model.
Definition slope.h:941
void setModifyX(const bool modify_x)
Controls if x should be modified-in-place.
void setQ(double q)
Sets the q value.
Eigen compatibility layer for version differences.
Cross-validation fold management for SLOPE models.
Namespace containing SLOPE regression implementation.
Definition clusters.h:11
std::unique_ptr< Loss > setupLoss(const std::string &loss)
Factory function to create the appropriate loss function based on the distribution family.
CvResult crossValidate(Slope model, Eigen::EigenBase< T > &x, const Eigen::MatrixXd &y_in, const CvConfig &config=CvConfig())
Performs cross-validation on a SLOPE model to select optimal hyperparameters.
Definition cv.h:249
Eigen::VectorXd stdDevs(const Eigen::SparseMatrixBase< T > &x)
Computes the standard deviation for each column of a matrix.
Definition math.h:747
Scoring metrics for model evaluation.
SLOPE (Sorted L-One Penalized Estimation) optimization.
Configuration settings for cross-validation.
Definition cv.h:91
int n_repeats
Number of times to repeat the cross-validation (default: 1)
Definition cv.h:96
bool copy_x
Whether to copy the design matrix for each fold (default: true)
Definition cv.h:105
std::map< std::string, std::vector< double > > hyperparams
Map of hyperparameter names to vectors of values to evaluate.
Definition cv.h:108
std::optional< std::vector< std::vector< std::vector< int > > > > predefined_folds
Optional user-defined fold assignments for custom cross-validation splits.
Definition cv.h:117
int n_folds
Number of folds for cross-validation (default: 10)
Definition cv.h:93
std::string metric
Evaluation metric used for model assessment (default: "mse")
Definition cv.h:99
std::map< std::string, std::vector< double > > default_hyperparams
Map of hyperparameter names to vectors of values to evaluate.
Definition cv.h:111
uint64_t random_seed
Seed for random number generator to ensure reproducibility (default: 42)
Definition cv.h:102
Contains overall results from a cross-validation process.
Definition cv.h:63
double best_score
The score achieved by the optimal hyperparameter configuration.
Definition cv.h:73
std::map< std::string, double > best_params
Definition cv.h:70
std::vector< GridResult > results
Definition cv.h:66
int best_ind
Index of the best performing configuration in the results vector.
Definition cv.h:76
int best_alpha_ind
Definition cv.h:80
Stores cross-validation results for a specific set of hyperparameters.
Definition cv.h:35
Eigen::MatrixXd score
Definition cv.h:39
Eigen::ArrayXd mean_scores
Array of scores averaged across all folds for each alpha value.
Definition cv.h:48
std::map< std::string, double > params
Map of hyperparameter names to their values for the configuration.
Definition cv.h:42
Eigen::ArrayXd std_errors
Definition cv.h:52
Eigen::ArrayXd alphas
Array of regularization parameters used in the regularization path.
Definition cv.h:45