slope 6.5.4
Loading...
Searching...
No Matches
slope.h
Go to the documentation of this file.
1
6#pragma once
7
8#include "clusters.h"
9#include "constants.h"
10#include "diagnostics.h"
11#include "estimate_alpha.h"
12#include "logger.h"
13#include "losses/loss.h"
14#include "losses/setup_loss.h"
15#include "math.h"
16#include "normalize.h"
18#include "screening.h"
19#include "slope_fit.h"
20#include "slope_path.h"
21#include "solvers/hybrid_cd.h"
23#include "sorted_l1_norm.h"
24#include "timer.h"
25#include <Eigen/Core>
26#include <Eigen/SparseCore>
27#include <cassert>
28#include <cmath>
29#include <limits>
30#include <memory>
31#include <optional>
32
36namespace slope {
37
42inline bool
44{
45 return false;
46}
47
56class Slope
57{
58public:
64 Slope() = default;
65
74 void setSolver(const std::string& solver);
75
81 void setIntercept(bool intercept);
82
90 void setNormalization(const std::string& type);
91
102 void setUpdateClusters(bool update_clusters);
103
110 void setReturnClusters(const bool return_clusters);
111
119 void setAlphaMinRatio(double alpha_min_ratio);
120
135 void setAlphaType(const std::string& alpha_type);
136
143 void setLearningRateDecr(double learning_rate_decr);
144
151 void setQ(double q);
152
159 void setOscarParameters(const double theta1, const double theta2);
160
166 void setTol(double tol);
167
173 void setRelaxTol(double tol);
174
183
191
199 void setMaxIterations(int max_it);
200
206 void setPathLength(int path_length);
207
215 void setHybridCdIterations(int cd_iterations);
216
222 void setHybridCdType(const std::string& cd_type);
223
231 void setLambdaType(const std::string& lambda_type);
232
243 void setLoss(const std::string& loss_type);
244
254 void setScreening(const std::string& screening_type);
255
263 void setModifyX(const bool modify_x);
264
269 void setDevChangeTol(const double dev_change_tol);
270
276 void setDevRatioTol(const double dev_ratio_tol);
277
285 void setMaxClusters(const int max_clusters);
286
291 void setCentering(const std::string& type);
292
298 void setCentering(const Eigen::VectorXd& x_centers);
299
304 void setScaling(const std::string& type);
305
312 void setDiagnostics(const bool collect_diagnostics);
313
319 void setScaling(const Eigen::VectorXd& x_scales);
320
331 void setAlphaEstimationMaxIterations(const int alpha_est_maxit);
332
338 void setRandomSeed(const int seed);
339
345 void setRandomSeed(std::optional<int> seed);
346
350 bool hasRandomSeed() const;
351
355 int getRandomSeed() const;
356
362
366 bool getFitIntercept() const;
367
372 const std::string& getLossType();
373
392 template<typename T>
394 Eigen::EigenBase<T>& x,
395 const Eigen::MatrixXd& y_in,
396 Eigen::ArrayXd alpha = Eigen::ArrayXd::Zero(0),
397 Eigen::ArrayXd lambda = Eigen::ArrayXd::Zero(0),
398 std::function<bool()> check_interrupt = defaultInterruptChecker)
399 {
400 using Eigen::MatrixXd;
401 using Eigen::VectorXd;
402
403 const int n = x.rows();
404 const int p = x.cols();
405
406 const int INTERRUPT_FREQ = 100;
407 bool interrupt = false;
408
409 if (n != y_in.rows()) {
410 throw std::invalid_argument(
411 "x and y_in must have the same number of rows");
412 }
413
414 if (!isFinite(x.derived())) {
415 throw std::invalid_argument("x must not contain NA, NaN, or Inf values");
416 }
417
418 if (!y_in.array().isFinite().all()) {
419 throw std::invalid_argument("y must not contain NA, NaN, or Inf values");
420 }
421
422 auto jit_normalization = normalize(x.derived(),
423 this->x_centers,
424 this->x_scales,
425 this->centering_type,
426 this->scaling_type,
427 this->modify_x);
428
429 std::unique_ptr<Loss> loss = setupLoss(this->loss_type);
430
431 MatrixXd y = loss->preprocessResponse(y_in);
432
433 const int m = y.cols();
434
435 VectorXd beta0 = VectorXd::Zero(m);
436 VectorXd beta = VectorXd::Zero(p * m);
437
438 MatrixXd eta = MatrixXd::Zero(n, m); // linear predictor
439
440 if (this->intercept) {
441 beta0 = loss->link(y.colwise().mean()).transpose();
442 eta.rowwise() = beta0.transpose();
443 }
444
445 MatrixXd residual = loss->residual(eta, y);
446 VectorXd gradient(beta.size());
447
448 // Path data
449 bool user_alpha = alpha.size() > 0;
450 bool user_lambda = lambda.size() > 0;
451
452 if (!user_lambda) {
453 lambda = lambdaSequence(
454 p * m, this->q, this->lambda_type, n, this->theta1, this->theta2);
455 } else {
456 if (lambda.size() != beta.size()) {
457 throw std::invalid_argument(
458 "lambda must be the same length as the number of coefficients");
459 }
460 if (lambda.minCoeff() < 0) {
461 throw std::invalid_argument("lambda must be non-negative");
462 }
463 if (!lambda.isFinite().all()) {
464 throw std::invalid_argument("lambda must be finite");
465 }
466 // Check that lambda is in decreasing order
467 for (int i = 1; i < lambda.size(); ++i) {
468 if (lambda(i) > lambda(i - 1)) {
469 throw std::invalid_argument("lambda must be in decreasing order");
470 }
471 }
472 }
473
474 // Setup the regularization sequence and path
475 SortedL1Norm sl1_norm;
476
477 // TODO: Make this part of the slope class
478 auto solver = setupSolver(this->solver_type,
479 this->loss_type,
480 jit_normalization,
481 this->intercept,
482 this->update_clusters,
483 this->cd_iterations,
484 this->cd_type,
485 this->random_seed);
486
487 updateGradient(gradient,
488 x.derived(),
489 residual,
490 this->x_centers,
491 this->x_scales,
492 Eigen::VectorXd::Ones(n),
493 jit_normalization);
494
495 int alpha_max_ind = whichMax(gradient.cwiseAbs());
496 double alpha_max =
497 lambda.maxCoeff() == 0.0 ? 0.0 : sl1_norm.dualNorm(gradient, lambda);
498 const double numerical_stationarity_tol =
499 std::sqrt(std::numeric_limits<double>::epsilon()) *
500 std::max(1.0, gradient.cwiseAbs().maxCoeff());
501
502 if (alpha_type == "path" ||
503 (alpha_type == "estimate" && alpha_estimate != 1)) {
504 if (alpha_min_ratio < 0) {
505 alpha_min_ratio = n > gradient.size() ? 1e-4 : 1e-2;
506 }
507
508 alpha =
509 regularizationPath(alpha, path_length, alpha_min_ratio, alpha_max);
510 path_length = alpha.size();
511 } else if (alpha_type == "estimate" && alpha_estimate == -1) {
512 if (loss_type != "quadratic") {
513 throw std::invalid_argument("Automatic alpha estimation is only "
514 "available for the quadratic loss");
515 }
516 }
517
518 // Screening setup
519 std::unique_ptr<ScreeningRule> screening_rule =
520 createScreeningRule(this->screening_type);
521 std::vector<int> working_set =
522 screening_rule->initialize(static_cast<int>(beta.size()), alpha_max_ind);
523
524 // Path variables
525 double null_deviance = loss->deviance(eta, y);
526 double dev_prev = null_deviance;
527
528 Timer timer;
529
530 double alpha_prev = std::max(alpha_max, alpha(0));
531
532 std::vector<SlopeFit> fits;
533 std::optional<MatrixXd> quadratic_ols_dual_point;
534
535 // Regularization path loop
536 for (int path_step = 0; path_step < this->path_length; ++path_step) {
537 // Check for interrupt at the start of each path step
538 bool local_interrupt = false;
539#ifdef _OPENMP
540#pragma omp critical(check_interrupt)
541#endif
542 {
543 local_interrupt = check_interrupt();
544 }
545 if (local_interrupt) {
546 interrupt = true;
547 break;
548 }
549
550 double alpha_curr = alpha(path_step);
551
552 assert(alpha_curr <= alpha_prev && "Alpha must be decreasing");
553
554 Eigen::ArrayXd lambda_curr = alpha_curr * lambda;
555 Eigen::ArrayXd lambda_prev = alpha_prev * lambda;
556
557 const bool near_unregularized =
558 lambda_curr.maxCoeff() == 0.0 ||
559 (alpha_max > 0.0 &&
560 alpha_curr <=
561 std::sqrt(std::numeric_limits<double>::epsilon()) * alpha_max);
562 // The QR residual avoids amplifying roundoff in the loss gradient when
563 // the penalty is below numerical resolution. It remains only a candidate
564 // until computeDualFromPoint checks and, if necessary, scales it below.
565 if (loss_type == "quadratic" && near_unregularized &&
566 !quadratic_ols_dual_point.has_value()) {
567 quadratic_ols_dual_point =
568 detail::quadraticOlsDualPoint(x.derived(),
569 y,
570 this->x_centers,
571 this->x_scales,
572 jit_normalization,
573 this->intercept);
574 }
575
576 std::optional<double> quadratic_ols_dual;
577 if (quadratic_ols_dual_point.has_value()) {
578 quadratic_ols_dual = lambda_curr.maxCoeff() == 0.0
579 ? 0.0
581 *quadratic_ols_dual_point,
582 loss,
583 sl1_norm,
584 lambda_curr,
585 x.derived(),
586 y,
587 this->x_centers,
588 this->x_scales,
589 jit_normalization);
590 }
591
592 std::vector<double> duals, primals, time;
593 timer.start();
594
595 // Update gradient for the full set
596 // TODO: Only update for non-working set since gradient is updated before
597 // the convergence check in the inner loop for the working set
598 updateGradient(gradient,
599 x.derived(),
600 residual,
601 x_centers,
602 x_scales,
603 Eigen::VectorXd::Ones(x.rows()),
604 jit_normalization);
605
606 screening_rule->screen(
607 working_set, gradient, lambda_curr, lambda_prev, beta);
608
609 int it = 0;
610 int total_it = 0;
611 for (; it < this->max_it; ++it, ++total_it) {
612 // Compute primal, dual, and gap
613 residual = loss->residual(eta, y);
614 updateGradient(gradient,
615 x.derived(),
616 residual,
617 working_set,
618 this->x_centers,
619 this->x_scales,
620 Eigen::VectorXd::Ones(n),
621 jit_normalization);
622
623 double primal = loss->loss(eta, y) +
624 sl1_norm.eval(beta(working_set),
625 lambda_curr.head(working_set.size()));
626
627 // The OLS bound can be loose on correlated designs, so it may certify
628 // convergence only after the loss itself is numerically stationary.
629 const bool numerically_stationary =
630 gradient(working_set).cwiseAbs().maxCoeff() <=
631 numerical_stationarity_tol &&
632 (!this->intercept ||
633 residual.colwise().mean().cwiseAbs().maxCoeff() <=
634 numerical_stationarity_tol);
635
636 MatrixXd dual_point = loss->dualPoint(eta, y, this->intercept);
637 MatrixXd theta = dual_point;
638 VectorXd dual_gradient = VectorXd::Zero(beta.size());
639 updateGradient(dual_gradient,
640 x.derived(),
641 theta,
642 working_set,
643 this->x_centers,
644 this->x_scales,
645 Eigen::VectorXd::Ones(n),
646 jit_normalization);
647 double dual_norm =
648 sl1_norm.dualNorm(dual_gradient(working_set),
649 lambda_curr.head(working_set.size()),
651 theta.array() /= std::max(1.0, dual_norm);
652 double dual = loss->dual(theta, y, Eigen::VectorXd::Ones(n));
653 if (quadratic_ols_dual.has_value() && numerically_stationary) {
654 dual = std::max(dual, *quadratic_ols_dual);
655 }
656
657 double full_dual = std::numeric_limits<double>::quiet_NaN();
658
659 if (collect_diagnostics) {
660 timer.pause();
661 full_dual = computeDualFromPoint(beta,
662 dual_point,
663 loss,
664 sl1_norm,
665 lambda_curr,
666 x.derived(),
667 y,
668 this->x_centers,
669 this->x_scales,
670 jit_normalization);
671 if (quadratic_ols_dual.has_value() && numerically_stationary) {
672 full_dual = std::max(full_dual, *quadratic_ols_dual);
673 }
674 timer.resume();
675
676 time.emplace_back(timer.elapsed());
677 primals.emplace_back(primal);
678 duals.emplace_back(full_dual);
679 }
680
681 double dual_gap = primal - dual;
682
683 assert(dual_gap > -1e-6 && "Dual gap should be positive");
684
685 double tol_scaled = (std::abs(primal) + constants::EPSILON) * this->tol;
686 const bool unregularized_stationary = loss_type == "quadratic" &&
687 lambda_curr.maxCoeff() == 0.0 &&
688 numerically_stationary;
689
690 if (dual_gap <= tol_scaled || unregularized_stationary ||
691 it == this->max_it) {
692 bool no_violations =
693 screening_rule->checkKktViolations(gradient,
694 beta,
695 lambda_curr,
696 working_set,
697 x.derived(),
698 residual,
699 this->x_centers,
700 this->x_scales,
701 jit_normalization);
702 if (no_violations) {
703 if (!std::isfinite(full_dual)) {
704 full_dual = computeDualFromPoint(beta,
705 dual_point,
706 loss,
707 sl1_norm,
708 lambda_curr,
709 x.derived(),
710 y,
711 this->x_centers,
712 this->x_scales,
713 jit_normalization);
714 if (quadratic_ols_dual.has_value() && numerically_stationary) {
715 full_dual = std::max(full_dual, *quadratic_ols_dual);
716 }
717 }
718 if (primal - full_dual <= tol_scaled || unregularized_stationary) {
719 break;
720 }
721 } else {
722 it = 0; // Restart if there are KKT violations
723 }
724 }
725
726 if (it % INTERRUPT_FREQ == 0) {
727 bool local_interrupt = false;
728#ifdef _OPENMP
729#pragma omp critical(check_interrupt)
730#endif
731 {
732 local_interrupt = check_interrupt();
733 }
734 if (local_interrupt) {
735 interrupt = true;
736 break;
737 }
738 }
739
740 solver->run(beta0,
741 beta,
742 eta,
743 lambda_curr,
744 loss,
745 sl1_norm,
746 gradient,
747 working_set,
748 x.derived(),
749 this->x_centers,
750 this->x_scales,
751 y);
752 }
753
754 if (it == this->max_it) {
757 "Maximum number of iterations reached at step = " +
758 std::to_string(path_step) + ".");
759 }
760
761 alpha_prev = alpha_curr;
762
763 // Compute early stopping criteria
764 double dev = loss->deviance(eta, y);
765 double dev_ratio = 1 - dev / null_deviance;
766 double dev_change = path_step == 0 ? 1.0 : 1 - dev / dev_prev;
767 dev_prev = dev;
768
769 Clusters clusters;
770
771 if (return_clusters) {
772 clusters.update(beta);
773 }
774
775 SlopeFit fit{ beta0,
776 beta.reshaped(p, m).sparseView(),
777 clusters,
778 alpha_curr,
779 lambda,
780 dev,
781 null_deviance,
782 primals,
783 duals,
784 time,
785 total_it,
786 this->centering_type,
787 this->scaling_type,
788 this->intercept,
789 this->x_centers,
790 this->x_scales };
791
792 fits.emplace_back(std::move(fit));
793
794 if (interrupt) {
795 break;
796 }
797
798 if (!user_alpha) {
799 int n_unique = unique(beta.cwiseAbs()).size();
800 if (dev_ratio > dev_ratio_tol || dev_change < dev_change_tol ||
801 n_unique >= this->max_clusters.value_or(n + 1)) {
802 break;
803 }
804 }
805 }
806
807 return fits;
808 }
809
827 template<typename T>
828 SlopeFit fit(Eigen::EigenBase<T>& x,
829 const Eigen::MatrixXd& y_in,
830 const double alpha = 1.0,
831 Eigen::ArrayXd lambda = Eigen::ArrayXd::Zero(0),
832 std::function<bool()> check_interrupt = defaultInterruptChecker)
833 {
834 Eigen::ArrayXd alpha_arr(1);
835 alpha_arr(0) = alpha;
836 SlopePath res = path(x, y_in, alpha_arr, lambda, check_interrupt);
837
838 return { res(0) };
839 };
840
865 template<typename T>
867 Eigen::EigenBase<T>& x,
868 Eigen::MatrixXd& y,
869 std::function<bool()> check_interrupt = defaultInterruptChecker)
870 {
871 int n = x.rows();
872 int p = x.cols();
873
874 // Create a copy with alpha type set to path to avoid recursion
875 Slope model_copy = *this;
876 model_copy.setAlphaType("path");
877
878 std::vector<int> selected;
879 Eigen::ArrayXd alpha(1);
880 SlopePath result;
881
882 // Estimate the noise level, if possible
883 if (n >= p + 30) {
884 alpha(0) = estimateNoise(x, y, this->intercept) / n;
885 this->alpha_estimate = alpha(0);
886 result =
887 model_copy.path(x, y, alpha, Eigen::ArrayXd::Zero(0), check_interrupt);
888 } else {
889 for (int it = 0; it < this->alpha_est_maxit; ++it) {
890 T x_selected = subsetCols(x.derived(), selected);
891
892 std::vector<int> selected_prev = selected;
893 selected.clear();
894
895 alpha(0) = estimateNoise(x_selected, y, this->intercept) / n;
896 this->alpha_estimate = alpha(0);
897
898 result = model_copy.path(
899 x, y, alpha, Eigen::ArrayXd::Zero(0), check_interrupt);
900 auto coefs = result.getCoefs().back();
901
902 for (typename Eigen::SparseMatrix<double>::InnerIterator it(coefs, 0);
903 it;
904 ++it) {
905 selected.emplace_back(it.row());
906 }
907
908 if (selected == selected_prev) {
909 return result;
910 }
911
912 if (static_cast<int>(selected.size()) >= n + this->intercept) {
913 throw std::runtime_error(
914 "selected >= n - 1 variables, cannot estimate variance");
915 }
916 }
917
920 "Maximum iterations reached in alpha estimation");
921 }
922
923 return result;
924 }
925
940 template<typename T>
942 T& x,
943 const Eigen::VectorXd& y_in,
944 const double gamma = 0.0,
945 Eigen::VectorXd beta0 = Eigen::VectorXd(0),
946 Eigen::VectorXd beta = Eigen::VectorXd(0))
947 {
948 using Eigen::MatrixXd;
949 using Eigen::VectorXd;
950
951 int n = x.rows();
952 int p = x.cols();
953
954 if (beta0.size() == 0) {
955 beta0 = fit.getIntercepts(false);
956 }
957
958 if (beta.size() == 0) {
959 beta = fit.getCoefs(false);
960 }
961
962 double alpha = fit.getAlpha();
963
964 Timer timer;
965
966 std::vector<double> primals, duals, time;
967 timer.start();
968
969 auto jit_normalization =
970 normalize(x, x_centers, x_scales, centering_type, scaling_type, modify_x);
971
972 bool update_clusters = false;
973
974 std::unique_ptr<Loss> loss = setupLoss(this->loss_type);
975
976 MatrixXd y = loss->preprocessResponse(y_in);
977
978 int m = y.cols();
979
980 Eigen::ArrayXd lambda_cumsum_relax = Eigen::ArrayXd::Zero(p * m + 1);
981
982 auto working_set = activeSet(beta);
983
984 Eigen::MatrixXd eta = linearPredictor(x,
985 working_set,
986 beta0,
987 beta,
988 x_centers,
989 x_scales,
990 jit_normalization,
991 intercept);
992 VectorXd gradient = VectorXd::Zero(p * m);
993 MatrixXd residual(n, m);
994 MatrixXd working_residual(n, m);
995
996 MatrixXd w = MatrixXd::Ones(n, m);
997 MatrixXd w_ones = MatrixXd::Ones(n, m);
998 MatrixXd z = y;
999
1000 slope::Clusters clusters(beta);
1001
1002 std::mt19937 rng;
1003
1004 if (random_seed.has_value()) {
1005 rng.seed(*random_seed);
1006 } else {
1007 rng.seed(std::random_device{}());
1008 }
1009
1010 int passes = 0;
1011
1012 for (int irls_it = 0; irls_it < max_it_outer_relax; irls_it++) {
1013 residual = loss->residual(eta, y);
1014
1015 if (collect_diagnostics) {
1016 primals.push_back(loss->loss(eta, y));
1017 duals.push_back(0.0);
1018 time.push_back(timer.elapsed());
1019 }
1020
1021 Eigen::VectorXd cluster_gradient = clusterGradient(beta,
1022 residual,
1023 clusters,
1024 x,
1025 w_ones,
1026 x_centers,
1027 x_scales,
1028 jit_normalization);
1029
1030 double norm_grad = cluster_gradient.lpNorm<Eigen::Infinity>();
1031
1032 if (norm_grad < tol_relax) {
1033 break;
1034 }
1035
1036 loss->updateWeightsAndWorkingResponse(w, z, eta, y);
1037 working_residual = eta - z;
1038 const VectorXd weight_sums = w.colwise().sum().transpose();
1039
1040 for (int inner_it = 0; inner_it < max_it_inner_relax; ++inner_it) {
1041 passes++;
1042
1043 double max_abs_gradient = coordinateDescent(beta0,
1044 beta,
1045 working_residual,
1046 clusters,
1047 lambda_cumsum_relax,
1048 x,
1049 w,
1050 weight_sums,
1051 x_centers,
1052 x_scales,
1053 intercept,
1054 jit_normalization,
1055 update_clusters,
1056 rng,
1057 cd_type);
1058
1059 if (max_abs_gradient < tol_relax) {
1060 break;
1061 }
1062 }
1063
1064 eta = working_residual + z;
1065
1066 if (irls_it == max_it_outer_relax) {
1068 "Maximum number of IRLS iterations reached.");
1069 }
1070 }
1071
1072 double dev = loss->deviance(eta, y);
1073
1074 if (gamma > 0) {
1075 Eigen::VectorXd old_coefs = fit.getCoefs(false);
1076 Eigen::VectorXd old_intercept = fit.getIntercepts(false);
1077 beta = (1 - gamma) * beta + gamma * old_coefs;
1078 }
1079
1080 SlopeFit fit_out{ beta0,
1081 beta.reshaped(p, m).sparseView(),
1082 clusters,
1083 alpha,
1084 fit.getLambda(),
1085 dev,
1087 primals,
1088 duals,
1089 time,
1090 passes,
1091 centering_type,
1092 scaling_type,
1093 intercept,
1094 x_centers,
1095 x_scales };
1096
1097 return fit_out;
1098 }
1099
1112 template<typename T>
1114 T& x,
1115 const Eigen::VectorXd& y,
1116 const double gamma = 0.0)
1117 {
1118 std::vector<SlopeFit> fits;
1119
1120 for (size_t i = 0; i < path.size(); i++) {
1121 // TODO: Reinstate warm starts. Need to be careful about
1122 // the warm started values though since they have to
1123 // agree with the cluster or we will run into trouble.
1124 // We can probably fix this by using the signs
1125 // of the cluster object rather than the betas though.
1126 auto relaxed_fit = relax(path(i), x, y, gamma);
1127
1128 fits.emplace_back(relaxed_fit);
1129 }
1130
1131 return fits;
1132 }
1133
1134private:
1135 // Parameters
1136 bool collect_diagnostics = false;
1137 bool intercept = true;
1138 bool modify_x = false;
1139 bool return_clusters = true;
1140 bool update_clusters = true;
1141 double alpha_min_ratio = -1;
1142 double dev_change_tol = 1e-5;
1143 double dev_ratio_tol = 0.999;
1144 double learning_rate_decr = 0.5;
1145 double q = 0.1;
1146 double theta1 = 1.0;
1147 double theta2 = 0.5;
1148 double tol = 1e-4;
1149 double tol_relax = 1e-4;
1150 double alpha_estimate = -1;
1151 int alpha_est_maxit = 1000;
1152 int cd_iterations = 10;
1153 int max_it = 1e5;
1154 int max_it_inner_relax = 1e5;
1155 int max_it_outer_relax = 50;
1156 int path_length = 100;
1157 std::optional<int> max_clusters = std::nullopt;
1158 std::optional<int> random_seed = 0;
1159 std::string alpha_type = "path";
1160 std::string cd_type = "permuted";
1161 std::string centering_type = "mean";
1162 std::string lambda_type = "bh";
1163 std::string loss_type = "quadratic";
1164 std::string scaling_type = "sd";
1165 std::string screening_type = "strong";
1166 std::string solver_type = "auto";
1167
1168 // Data
1169 Eigen::VectorXd x_centers;
1170 Eigen::VectorXd x_scales;
1171};
1172
1173} // namespace slope
Representation of the nonzero clusters in SLOPE.
Definition clusters.h:23
void update(const int old_index, const int new_index, const double c_new)
Updates the cluster structure when an index is changed.
A class representing the results of SLOPE (Sorted L1 Penalized Estimation) fitting.
Definition slope_fit.h:27
Eigen::VectorXd getIntercepts(const bool original_scale=true) const
Gets the intercept terms for this SLOPE fit.
Definition slope_fit.h:113
const Eigen::ArrayXd & getLambda() const
Gets the lambda (regularization) parameter used.
Definition slope_fit.h:156
double getAlpha() const
Gets the alpha (mixing) parameter used.
Definition slope_fit.h:161
Eigen::SparseMatrix< double > getCoefs(const bool original_scale=true) const
Gets the sparse coefficient matrix for this fit.
Definition slope_fit.h:133
double getNullDeviance() const
Gets the null model deviance.
Definition slope_fit.h:176
Container class for SLOPE regression solution paths.
Definition slope_path.h:32
std::size_t size() const
Gets the number of solutions in the path.
Definition slope_path.h:256
std::vector< Eigen::SparseMatrix< double > > getCoefs(const bool original_scale=true) const
Returns the vector of coefficient matrices for each solution in the path.
Definition slope_path.h:97
The SLOPE model.
Definition slope.h:57
void setSolver(const std::string &solver)
Sets the numerical solver used to fit the model.
void setAlphaMinRatio(double alpha_min_ratio)
Sets the alpha min ratio.
void setMaxIterations(int max_it)
Sets the maximum number of iterations.
void setAlphaEstimationMaxIterations(const int alpha_est_maxit)
Sets the maximum number of iterations for the alpha estimation procedure.
void setRelaxMaxInnerIterations(int max_it)
Sets the maximum number of inner iterations for the relaxed solver.
void setRandomSeed(std::optional< int > seed)
Sets the random seed.
int getRandomSeed() const
Gets the random seed.
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
SlopePath estimateAlpha(Eigen::EigenBase< T > &x, Eigen::MatrixXd &y, std::function< bool()> check_interrupt=defaultInterruptChecker)
Estimates the regularization parameter alpha for SLOPE regression.
Definition slope.h:866
void setDevRatioTol(const double dev_ratio_tol)
Sets tolerance in deviance change for early stopping.
void setScaling(const std::string &type)
Sets the scaling type.
void setRandomSeed(const int seed)
Sets the random seed.
void setDevChangeTol(const double dev_change_tol)
Sets tolerance in deviance change for early stopping.
const std::string & getLossType()
Get currently defined loss type.
void setReturnClusters(const bool return_clusters)
Sets the return clusters flag.
void setRelaxMaxOuterIterations(int max_it)
Sets the maximum number of outer (IRLS) iterations for the relaxed solver.
void setMaxClusters(const int max_clusters)
Sets the maximum number of clusters.
void setIntercept(bool intercept)
Sets the intercept flag.
bool getFitIntercept() const
Returns the intercept flag.
void setDiagnostics(const bool collect_diagnostics)
Toggles collection of diagnostics.
void setNormalization(const std::string &type)
Sets normalization type for the design matrix.
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
int getAlphaEstimationMaxIterations() const
Gets the maximum number of iterations allowed for the alpha estimation procedure.
void setScreening(const std::string &screening_type)
Sets the type of feature screening used, which discards predictors that are unlikely to be active.
void setOscarParameters(const double theta1, const double theta2)
Sets OSCAR parameters.
void setLambdaType(const std::string &lambda_type)
Sets the lambda type for regularization weights.
void setAlphaType(const std::string &alpha_type)
Sets the alpha type.
SlopeFit fit(Eigen::EigenBase< T > &x, const Eigen::MatrixXd &y_in, const double alpha=1.0, Eigen::ArrayXd lambda=Eigen::ArrayXd::Zero(0), std::function< bool()> check_interrupt=defaultInterruptChecker)
Fits a single SLOPE regression model for given alpha and lambda values.
Definition slope.h:828
void setModifyX(const bool modify_x)
Controls if x should be modified-in-place.
void setCentering(const std::string &type)
Sets the center points for feature normalization.
Slope()=default
void setHybridCdType(const std::string &cd_type)
Sets the frequence of proximal gradient descent steps.
bool hasRandomSeed() const
Checks if a random seed is set.
void setLearningRateDecr(double learning_rate_decr)
Sets the learning rate decrement.
void setLoss(const std::string &loss_type)
Sets the loss function type.
SlopePath relax(const SlopePath &path, T &x, const Eigen::VectorXd &y, const double gamma=0.0)
Relaxes a fitted SLOPE path.
Definition slope.h:1113
void setScaling(const Eigen::VectorXd &x_scales)
Sets the scaling factors for feature normalization.
void setRelaxTol(double tol)
Sets the tolerance value for the relaxed SLOPE solver.
void setPathLength(int path_length)
Sets the path length.
void setCentering(const Eigen::VectorXd &x_centers)
Sets the center points for feature normalization.
void setHybridCdIterations(int cd_iterations)
Sets the frequence of proximal gradient descent steps.
void setTol(double tol)
Sets the tolerance value.
void setQ(double q)
Sets the q value.
void setUpdateClusters(bool update_clusters)
Sets the update clusters flag.
Class representing the Sorted L1 Norm.
double eval(const Eigen::VectorXd &beta, const Eigen::ArrayXd &lambda) const
Evaluates the Sorted L1 Norm.
double dualNorm(const Eigen::VectorXd &a, const Eigen::ArrayXd &lambda) const
Computes the dual norm of a vector.
Timer class for measuring elapsed time with high resolution.
Definition timer.h:19
void start()
Starts the timer by recording the current time point.
void resume()
Resumes the timer after a pause.
double elapsed() const
Returns the elapsed time in seconds since start() was called.
void pause()
Pauses the timer.
static void addWarning(WarningCode code, const std::string &message)
Log a new warning.
The declaration of the Clusters class.
Definitions of constants used in libslope.
Diagnostics for SLOPE optimization.
Functions for estimating noise level and regularization parameter alpha.
An implementation of the coordinate descent step in the hybrid algorithm for solving SLOPE.
Thread-safe warning logging facility for the slope library.
The declartion of the Objctive class and its subclasses, which represent the data-fitting part of the...
Mathematical support functions for the slope package.
constexpr double EPSILON
Small value used for floating-point comparisons to handle precision issues.
Definition constants.h:27
constexpr double MAX_DIV
Maximum allowed divisor.
Definition constants.h:45
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:546
Eigen::ArrayXd regularizationPath(const Eigen::ArrayXd &alpha_in, const int path_length, double alpha_min_ratio, const double alpha_max)
std::unique_ptr< SolverBase > setupSolver(const std::string &solver_type, const std::string &loss, JitNormalization jit_normalization, bool intercept, bool update_clusters, int cd_iterations, const std::string &cd_type, std::optional< int > random_seed=std::nullopt)
Factory function to create and configure a SLOPE solver.
double computeDualFromPoint(const Eigen::VectorXd &beta, Eigen::MatrixXd theta, const std::unique_ptr< Loss > &loss, const SortedL1Norm &sl1_norm, const Eigen::ArrayXd &lambda, const MatrixType &x, const Eigen::MatrixXd &y, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const JitNormalization &jit_normalization)
Scales a candidate into the SLOPE dual constraint and evaluates it.
std::unique_ptr< Loss > setupLoss(const std::string &loss)
Factory function to create the appropriate loss function based on the distribution family.
bool defaultInterruptChecker()
Default no-op interrupt checker.
Definition slope.h:43
std::unordered_set< double > unique(const Eigen::MatrixXd &x)
Create a set of unique values from an Eigen matrix.
Definition utils.h:381
double estimateNoise(Eigen::EigenBase< T > &x, Eigen::MatrixXd &y, const bool fit_intercept)
Estimates noise (standard error) in a linear model using OLS residuals.
T subsetCols(const Eigen::MatrixBase< T > &x, const std::vector< int > &indices)
Extract specified columns from a dense matrix.
Definition utils.h:332
std::unique_ptr< ScreeningRule > createScreeningRule(const std::string &screening_type)
Creates a screening rule based on the provided type.
@ MAXIT_REACHED
Maximum iterations reached without convergence.
Eigen::ArrayXd lambdaSequence(const int p, const double q, const std::string &type, const int n=-1, const double theta1=1.0, const double theta2=1.0)
void updateGradient(Eigen::VectorXd &gradient, const T &x, const Eigen::MatrixXd &residual, const std::vector< int > &active_set, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const Eigen::VectorXd &w, const JitNormalization jit_normalization)
Computes the gradient for selected coefficients.
Definition math.h:311
Eigen::VectorXd clusterGradient(Eigen::VectorXd &beta, Eigen::MatrixXd &residual, Clusters &clusters, const T &x, const Eigen::MatrixXd &w, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const JitNormalization jit_normalization)
Definition math.h:934
std::vector< int > activeSet(const Eigen::VectorXd &beta)
Identifies previously active variables.
JitNormalization normalize(Eigen::MatrixBase< T > &x, Eigen::VectorXd &x_centers, Eigen::VectorXd &x_scales, const std::string &centering_type, const std::string &scaling_type, const bool modify_x)
Definition normalize.h:123
int whichMax(const T &x)
Returns the index of the maximum element in a container.
Definition math.h:505
bool isFinite(const Eigen::DenseBase< Derived > &x)
Check if all elements in a dense matrix are finite.
Definition utils.h:405
Functions to normalize the design matrix and rescale coefficients in case the design was normalized.
Functions for generating regularization sequences for SLOPE.
Screening rules for SLOPE regression optimization.
Factory function to create the appropriate loss function based on.
Factory function to create and configure a SLOPE solver.
SLOPE (Sorted L-One Penalized Estimation) fitting results.
Defines the SlopePath class for storing and accessing SLOPE regression solution paths.
The declaration of the SortedL1Norm class.
Simple high-resolution timer class for performance measurements.