slope 6.6.0
Loading...
Searching...
No Matches
math.h
Go to the documentation of this file.
1
5
6#pragma once
7
8#include "clusters.h"
9#include "eigen_compat.h"
10#include "jit_normalization.h"
11#include "utils.h"
12#include <Eigen/Core>
13#include <Eigen/SparseCore>
14#include <algorithm>
15#include <cassert>
16#include <limits>
17#include <numeric>
18#include <vector>
19
20#ifdef _OPENMP
21#include "threads.h"
22#endif
23
24namespace slope {
25
38template<typename T>
39int
40sign(T val)
41{
42 return (T(0) < val) - (val < T(0));
43}
44
54template<typename T>
55Eigen::ArrayXd
56cumSum(const T& x, const bool leading_zero = false)
57{
58 const size_t start = leading_zero ? 1 : 0;
59 Eigen::ArrayXd out(x.size() + start);
60
61 if (leading_zero) {
62 out(0) = 0.0;
63 }
64
65 std::partial_sum(x.begin(), x.end(), out.begin() + start);
66
67 return out;
68}
69
79template<typename T>
80T
81sigmoid(const T& x)
82{
83 return 1.0 / (1.0 + std::exp(-x));
84}
85
95template<typename T>
96T
97logit(const T& x)
98{
99 assert(x > 0 && x < 1 && "Input must be in (0, 1)");
100
101 return std::log(x) - std::log1p(-x);
102}
103
113template<typename T>
114T
115clamp(const T& x, const T& lo, const T& hi)
116{
117 return x < lo ? lo : x > hi ? hi : x;
118}
119
126Eigen::VectorXd
127logSumExp(const Eigen::MatrixXd& a);
128
137Eigen::MatrixXd
138softmax(const Eigen::MatrixXd& x);
139
140/*
141 * Computes the gradient of the loss with respect to \f(\beta\f).
142 *
143 * @tparam T The type of the input matrix.
144 * @param x The input matrix.
145 * @param active_set Indicies for active set
146 * @param beta0 Intercept
147 * @param beta Coefficients
148 * @param x_centers The vector of center values for each column of x.
149 * @param x_scales The vector of scale values for each column of x.
150 * @param jit_normalization Type of JIT normalization.
151 * @param intercept Whether to fit an intercept.
152 * @return The computed gradient vector.
153 */
154template<typename T>
155Eigen::MatrixXd
156linearPredictor(const T& x,
157 const std::vector<int>& active_set,
158 const Eigen::VectorXd& beta0,
159 const Eigen::VectorXd& beta,
160 const Eigen::VectorXd& x_centers,
161 const Eigen::VectorXd& x_scales,
162 const JitNormalization jit_normalization,
163 const bool intercept)
164{
165 int n = x.rows();
166 int p = x.cols();
167 int m = beta0.size();
168
169 Eigen::MatrixXd eta = Eigen::MatrixXd::Zero(n, m);
170
171#ifdef _OPENMP
172 bool large_problem = active_set.size() > 100 && n * active_set.size() > 1e7;
173#pragma omp parallel num_threads(Threads::get()) if (large_problem)
174#endif
175 {
176 Eigen::MatrixXd eta_local = Eigen::MatrixXd::Zero(n, m);
177
178#ifdef _OPENMP
179#pragma omp for nowait
180#endif
181 for (int i = 0; i < static_cast<int>(active_set.size()); ++i) {
182 int ind = active_set[i];
183 auto [k, j] = std::div(ind, p);
184
185 switch (jit_normalization) {
187 eta_local.col(k) += x.col(j) * beta(ind) / x_scales(j);
188 eta_local.col(k).array() -= beta(ind) * x_centers(j) / x_scales(j);
189 break;
190
192 eta_local.col(k) += x.col(j) * beta(ind);
193 eta_local.col(k).array() -= beta(ind) * x_centers(j);
194 break;
195
197 eta_local.col(k) += x.col(j) * beta(ind) / x_scales(j);
198 break;
199
201 eta_local.col(k) += x.col(j) * beta(ind);
202 break;
203 }
204 }
205
206#ifdef _OPENMP
207#pragma omp critical
208#endif
209 {
210 eta += eta_local;
211 }
212 }
213
214 if (intercept) {
215 eta.rowwise() += beta0.transpose();
216 }
217
218 return eta;
219}
220
235namespace detail {
236
237template<typename T, typename IndexAt>
238void
239updateGradientImpl(Eigen::VectorXd& gradient,
240 const T& x,
241 const Eigen::MatrixXd& residual,
242 const int active_size,
243 const IndexAt& index_at,
244 const Eigen::VectorXd& x_centers,
245 const Eigen::VectorXd& x_scales,
246 const Eigen::VectorXd& w,
247 const JitNormalization jit_normalization)
248{
249 const int n = x.rows();
250 const int p = x.cols();
251 const int m = residual.cols();
252
253 assert(gradient.size() == p * m &&
254 "Gradient matrix has incorrect dimensions");
255
256 Eigen::MatrixXd weighted_residual(n, m);
257 Eigen::ArrayXd wr_sums(m);
258
259#ifdef _OPENMP
260 bool large_problem =
261 active_size > 100 && static_cast<long long>(n) * active_size > 1e5;
262#pragma omp parallel for num_threads(Threads::get()) if (large_problem)
263#endif
264 for (int k = 0; k < m; ++k) {
265 weighted_residual.col(k) = residual.col(k).cwiseProduct(w);
266 for (int i = 0; i < n; ++i) {
267 if (w(i) == 0.0) {
268 weighted_residual(i, k) = 0.0;
269 }
270 }
271 wr_sums(k) = weighted_residual.col(k).sum();
272 }
273
274#ifdef _OPENMP
275#pragma omp parallel for num_threads(Threads::get()) if (large_problem)
276#endif
277 for (int i = 0; i < active_size; ++i) {
278 int ind = index_at(i);
279 auto [k, j] = std::div(ind, p);
280
281 switch (jit_normalization) {
283 gradient(ind) =
284 (x.col(j).dot(weighted_residual.col(k)) - x_centers(j) * wr_sums(k)) /
285 (x_scales(j) * n);
286 break;
288 gradient(ind) =
289 (x.col(j).dot(weighted_residual.col(k)) - x_centers(j) * wr_sums(k)) /
290 n;
291 break;
293 gradient(ind) =
294 x.col(j).dot(weighted_residual.col(k)) / (x_scales(j) * n);
295 break;
297 gradient(ind) = x.col(j).dot(weighted_residual.col(k)) / n;
298 break;
299 }
300 }
301}
302
303} // namespace detail
304
316template<typename T>
317void
318updateGradient(Eigen::VectorXd& gradient,
319 const T& x,
320 const Eigen::MatrixXd& residual,
321 const std::vector<int>& active_set,
322 const Eigen::VectorXd& x_centers,
323 const Eigen::VectorXd& x_scales,
324 const Eigen::VectorXd& w,
325 const JitNormalization jit_normalization)
326{
327 detail::updateGradientImpl(
328 gradient,
329 x,
330 residual,
331 static_cast<int>(active_set.size()),
332 [&active_set](int i) { return active_set[i]; },
333 x_centers,
334 x_scales,
335 w,
336 jit_normalization);
337}
338
344template<typename T>
345void
346updateGradient(Eigen::VectorXd& gradient,
347 const T& x,
348 const Eigen::MatrixXd& residual,
349 const Eigen::VectorXd& x_centers,
350 const Eigen::VectorXd& x_scales,
351 const Eigen::VectorXd& w,
352 const JitNormalization jit_normalization)
353{
354 detail::updateGradientImpl(
355 gradient,
356 x,
357 residual,
358 static_cast<int>(gradient.size()),
359 [](int i) { return i; },
360 x_centers,
361 x_scales,
362 w,
363 jit_normalization);
364}
365
366/*
367 * Computes the gradient of the loss with respect to \f(\beta\f).
368 *
369 * @tparam T The type of the input matrix.
370 * @param gradient The residual vector.
371 * @param x The input matrix.
372 * @param offset Gradient offset
373 * @param active_set Indices for the active_set
374 * @param x_centers The vector of center values for each column of x.
375 * @param x_scales The vector of scale values for each column of x.
376 * @param jit_normalization Type of JIT normalization
377 * just-in-time.
378 */
379namespace detail {
380
381template<typename T, typename IndexAt>
382void
383offsetGradientImpl(Eigen::VectorXd& gradient,
384 const T& x,
385 const Eigen::VectorXd& offset,
386 const int active_size,
387 const IndexAt& index_at,
388 const Eigen::VectorXd& x_centers,
389 const Eigen::VectorXd& x_scales,
390 const JitNormalization jit_normalization)
391{
392 const int n = x.rows();
393 const int p = x.cols();
394
395 for (int i = 0; i < active_size; ++i) {
396 int ind = index_at(i);
397 auto [k, j] = std::div(ind, p);
398
399 switch (jit_normalization) {
401 gradient(ind) -=
402 offset(k) * (x.col(j).sum() / n - x_centers(j)) / x_scales(j);
403 break;
405 gradient(ind) -= offset(k) * (x.col(j).sum() / n - x_centers(j));
406 break;
408 gradient(ind) -= offset(k) * x.col(j).sum() / (n * x_scales(j));
409 break;
411 gradient(ind) -= offset(k) * x.col(j).sum() / n;
412 break;
413 }
414 }
415}
416
417} // namespace detail
418
429template<typename T>
430void
431offsetGradient(Eigen::VectorXd& gradient,
432 const T& x,
433 const Eigen::VectorXd& offset,
434 const std::vector<int>& active_set,
435 const Eigen::VectorXd& x_centers,
436 const Eigen::VectorXd& x_scales,
437 const JitNormalization jit_normalization)
438{
439 detail::offsetGradientImpl(
440 gradient,
441 x,
442 offset,
443 static_cast<int>(active_set.size()),
444 [&active_set](int i) { return active_set[i]; },
445 x_centers,
446 x_scales,
447 jit_normalization);
448}
449
455template<typename T>
456void
457offsetGradient(Eigen::VectorXd& gradient,
458 const T& x,
459 const Eigen::VectorXd& offset,
460 const Eigen::VectorXd& x_centers,
461 const Eigen::VectorXd& x_scales,
462 const JitNormalization jit_normalization)
463{
464 detail::offsetGradientImpl(
465 gradient,
466 x,
467 offset,
468 static_cast<int>(gradient.size()),
469 [](int i) { return i; },
470 x_centers,
471 x_scales,
472 jit_normalization);
473}
474
483std::vector<int>
484setUnion(const std::vector<int>& a, const std::vector<int>& b);
485
496std::vector<int>
497setDiff(const std::vector<int>& a, const std::vector<int>& b);
498
510template<typename T>
511int
512whichMax(const T& x)
513{
514 return std::distance(x.begin(), std::max_element(x.begin(), x.end()));
515}
516
528template<typename T>
529int
530whichMin(const T& x)
531{
532 return std::distance(x.begin(), std::min_element(x.begin(), x.end()));
533}
534
548template<typename T, typename Comparator>
549int
550whichBest(const T& x, const Comparator& comp)
551{
552 if (x.size() == 0)
553 return -1;
554
555 return std::distance(x.begin(), std::max_element(x.begin(), x.end(), comp));
556}
557
572Eigen::ArrayXd
573geomSpace(const double start, const double end, const int n);
574
587template<typename T>
588Eigen::VectorXd
589l1Norms(const T& x)
590{
591 const int p = x.cols();
592
593 Eigen::VectorXd out(p);
594
595 for (int j = 0; j < p; ++j) {
596 out(j) = x.col(j).cwiseAbs().sum();
597 }
598
599 return out;
600}
601
612template<typename T>
613Eigen::VectorXd
614l2Norms(const Eigen::SparseMatrixBase<T>& x)
615{
616 const int p = x.cols();
617
618 Eigen::VectorXd out(p);
619
620 for (int j = 0; j < p; ++j) {
621 out(j) = x.col(j).norm();
622 }
623
624 return out;
625}
626
637template<typename T>
638Eigen::VectorXd
639l2Norms(const Eigen::MatrixBase<T>& x)
640{
641 return x.colwise().norm();
642}
643
655template<typename T>
656Eigen::VectorXd
657maxAbs(const Eigen::SparseMatrixBase<T>& x)
658{
659 const int p = x.cols();
660
661 Eigen::VectorXd out(p);
662
663 for (int j = 0; j < p; ++j) {
664 double x_j_maxabs = 0.0;
665
666 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
667 x_j_maxabs = std::max(x_j_maxabs, std::abs(it.value()));
668 }
669
670 out(j) = x_j_maxabs;
671 }
672
673 return out;
674}
675
688template<typename T>
689Eigen::VectorXd
690maxAbs(const Eigen::MatrixBase<T>& x)
691{
692 return x.cwiseAbs().colwise().maxCoeff();
693}
694
706template<typename T>
707Eigen::VectorXd
708means(const Eigen::SparseMatrixBase<T>& x)
709{
710 const int n = x.rows();
711 const int p = x.cols();
712
713 Eigen::VectorXd out(p);
714
715 for (int j = 0; j < p; ++j) {
716 out(j) = x.col(j).sum() / n;
717 }
718
719 return out;
720}
721
733template<typename T>
734Eigen::VectorXd
735means(const Eigen::MatrixBase<T>& x)
736{
737 return x.colwise().mean();
738}
739
752template<typename T>
753Eigen::VectorXd
754stdDevs(const Eigen::SparseMatrixBase<T>& x)
755{
756 const int n = x.rows();
757 const int p = x.cols();
758
759 Eigen::VectorXd x_means = means(x);
760 Eigen::VectorXd out(p);
761
762 for (int j = 0; j < p; ++j) {
763 double sum_sq_diff = 0.0;
764 const double mean = x_means(j);
765 double minimum = std::numeric_limits<double>::infinity();
766 double maximum = -std::numeric_limits<double>::infinity();
767
768 // Process non-zero elements
769 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
770 double diff = it.value() - mean;
771 sum_sq_diff += diff * diff;
772 minimum = std::min(minimum, it.value());
773 maximum = std::max(maximum, it.value());
774 }
775
776 // Account for zeros
777 int nz_count = slope::nonZeros(x.col(j));
778 if (nz_count < n) {
779 sum_sq_diff += (n - nz_count) * mean * mean;
780 minimum = std::min(minimum, 0.0);
781 maximum = std::max(maximum, 0.0);
782 }
783
784 // Rounding in the mean must not give constant columns a nonzero scale.
785 if (minimum == maximum) {
786 out(j) = 0.0;
787 continue;
788 }
789
790 // Standard deviation is sqrt of the average of squared differences
791 out(j) = std::sqrt(sum_sq_diff / n);
792 }
793
794 return out;
795}
796
809template<typename T>
810Eigen::VectorXd
811stdDevs(const Eigen::MatrixBase<T>& x)
812{
813 int n = x.rows();
814 int p = x.cols();
815
816 Eigen::VectorXd x_means = means(x);
817 Eigen::VectorXd out(p);
818
819 for (int j = 0; j < p; ++j) {
820 // Rounding in the mean must not give constant columns a nonzero scale.
821 if (n > 0 && x.col(j).minCoeff() == x.col(j).maxCoeff()) {
822 out(j) = 0.0;
823 } else {
824 out(j) = (x.col(j).array() - x_means(j)).matrix().norm();
825 }
826 }
827
828 out.array() /= std::sqrt(n);
829
830 return out;
831}
832
843template<typename T>
844Eigen::VectorXd
845ranges(const Eigen::SparseMatrixBase<T>& x)
846{
847 const int p = x.cols();
848
849 Eigen::VectorXd out(p);
850
851 for (int j = 0; j < p; ++j) {
852 double x_j_max = 0.0;
853 double x_j_min = 0.0;
854
855 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
856 x_j_max = std::max(x_j_max, it.value());
857 x_j_min = std::min(x_j_min, it.value());
858 }
859
860 out(j) = x_j_max - x_j_min;
861 }
862
863 return out;
864}
865
879template<typename T>
880Eigen::VectorXd
881ranges(const Eigen::MatrixBase<T>& x)
882{
883 return x.colwise().maxCoeff() - x.colwise().minCoeff();
884}
885
896template<typename T>
897Eigen::VectorXd
898mins(const Eigen::SparseMatrixBase<T>& x)
899{
900 const int p = x.cols();
901
902 Eigen::VectorXd out(p);
903
904 for (int j = 0; j < p; ++j) {
905 double x_j_min = 0.0;
906
907 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
908 x_j_min = std::min(x_j_min, it.value());
909 }
910
911 out(j) = x_j_min;
912 }
913
914 return out;
915}
916
930template<typename T>
931Eigen::VectorXd
932mins(const Eigen::MatrixBase<T>& x)
933{
934 return x.colwise().minCoeff();
935}
936
956template<typename T>
957Eigen::VectorXd
958clusterGradient(Eigen::VectorXd& beta,
959 Eigen::MatrixXd& residual,
960 Clusters& clusters,
961 const T& x,
962 const Eigen::MatrixXd& w,
963 const Eigen::VectorXd& x_centers,
964 const Eigen::VectorXd& x_scales,
965 const JitNormalization jit_normalization)
966{
967 using namespace Eigen;
968
969 const int n = x.rows();
970 const int p = x.cols();
971 const int n_clusters = clusters.size();
972
973 Eigen::VectorXd gradient = Eigen::VectorXd::Zero(n_clusters);
974
975 for (int j = 0; j < n_clusters; ++j) {
976 double c_old = clusters.coeff(j);
977
978 if (c_old == 0) {
979 gradient(j) = 0;
980 continue;
981 }
982
983 int cluster_size = clusters.cluster_size(j);
984 std::vector<int> s;
985 s.reserve(cluster_size);
986
987 for (auto c_it = clusters.cbegin(j); c_it != clusters.cend(j); ++c_it) {
988 int ind = *c_it;
989 double s_k = sign(beta(ind));
990 s.emplace_back(s_k);
991 }
992
993 double hess = 1;
994 double grad = 0;
995
996 if (cluster_size == 1) {
997 int k = *clusters.cbegin(j);
998 std::tie(grad, hess) = computeGradientAndHessian(
999 x, k, w, residual, x_centers, x_scales, s[0], jit_normalization, n);
1000 } else {
1001 std::tie(hess, grad) = computeClusterGradientAndHessian(
1002 x, j, s, clusters, w, residual, x_centers, x_scales, jit_normalization);
1003 }
1004
1005 gradient(j) = grad;
1006 }
1007
1008 return gradient;
1009}
1010
1011} // namespace slope
Representation of the nonzero clusters in SLOPE.
Definition clusters.h:23
double coeff(const int i) const
Returns the coefficient of the cluster with the given index.
std::size_t size()
Returns the number of stored nonzero clusters.
Definition clusters.h:40
int cluster_size(const int i) const
Returns the size of the cluster with the given index.
std::vector< int >::const_iterator cend(const int i) const
Returns a constant iterator pointing to the end of the cluster with the given index.
std::vector< int >::const_iterator cbegin(const int i) const
Returns a constant iterator pointing to the beginning of the cluster with the given index.
The declaration of the Clusters class.
Eigen compatibility layer for version differences.
Enums to control predictor standardization behavior.
Namespace containing SLOPE regression implementation.
Definition clusters.h:11
Eigen::VectorXd means(const Eigen::SparseMatrixBase< T > &x)
Computes the arithmetic mean for each column of a sparse matrix.
Definition math.h:708
std::pair< double, double > computeGradientAndHessian(const Eigen::MatrixBase< T > &x, const int ind, const Eigen::MatrixXd &w, const Eigen::MatrixXd &residual, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const double s, const JitNormalization jit_normalization, const int n, const Eigen::VectorXd &weight_sums)
Definition hybrid_cd.h:106
T clamp(const T &x, const T &lo, const T &hi)
Definition math.h:115
Eigen::VectorXd l1Norms(const T &x)
Computes the L1 (Manhattan) norms for each column of a matrix.
Definition math.h:589
int sign(T val)
Returns the sign of a given value.
Definition math.h:40
int whichMin(const T &x)
Returns the index of the minimum element in a container.
Definition math.h:530
T sigmoid(const T &x)
Definition math.h:81
JitNormalization
Enums to control predictor standardization behavior.
@ None
No JIT normalization.
Eigen::VectorXd logSumExp(const Eigen::MatrixXd &a)
std::vector< int > setDiff(const std::vector< int > &a, const std::vector< int > &b)
Computes the set difference of two sorted integer vectors.
Eigen::VectorXd ranges(const Eigen::SparseMatrixBase< T > &x)
Computes the range (max - min) for each column of a matrix.
Definition math.h:845
Eigen::MatrixXd softmax(const Eigen::MatrixXd &x)
std::vector< int > setUnion(const std::vector< int > &a, const std::vector< int > &b)
Computes the union of two sorted integer vectors.
Eigen::VectorXd mins(const Eigen::SparseMatrixBase< T > &x)
Computes the minimum value for each column of a sparse matrix.
Definition math.h:898
Eigen::ArrayXd cumSum(const T &x, const bool leading_zero=false)
Definition math.h:56
int whichBest(const T &x, const Comparator &comp)
Returns the index of the minimum element in a container.
Definition math.h:550
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:318
void offsetGradient(Eigen::VectorXd &gradient, const T &x, const Eigen::VectorXd &offset, const std::vector< int > &active_set, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const JitNormalization jit_normalization)
Offsets the gradient for selected coefficients.
Definition math.h:431
T logit(const T &x)
Definition math.h:97
Eigen::VectorXd stdDevs(const Eigen::SparseMatrixBase< T > &x)
Computes the standard deviation for each column of a matrix.
Definition math.h:754
Eigen::VectorXd l2Norms(const Eigen::SparseMatrixBase< T > &x)
Computes the L2 (Euclidean) norms for each column of a sparse matrix.
Definition math.h:614
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:958
Eigen::VectorXd maxAbs(const Eigen::SparseMatrixBase< T > &x)
Computes the maximum absolute value for each column of a matrix.
Definition math.h:657
Eigen::ArrayXd geomSpace(const double start, const double end, const int n)
Creates an array of n numbers in geometric progression from start to end.
std::pair< double, double > computeClusterGradientAndHessian(const Eigen::MatrixBase< T > &x, const int c_ind, const std::vector< int > &s, const Clusters &clusters, const Eigen::MatrixXd &w, const Eigen::MatrixXd &residual, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const JitNormalization jit_normalization)
Definition hybrid_cd.h:315
int whichMax(const T &x)
Returns the index of the maximum element in a container.
Definition math.h:512
Eigen::Index nonZeros(const Eigen::SparseMatrixBase< Derived > &x)
Definition utils.h:34
Thread management for parallel computations.
Various utility functions.