slope 6.5.4
Loading...
Searching...
No Matches
math.h
Go to the documentation of this file.
1
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 <cassert>
15#include <numeric>
16#include <vector>
17
18#ifdef _OPENMP
19#include "threads.h"
20#endif
21
22namespace slope {
23
36template<typename T>
37int
38sign(T val)
39{
40 return (T(0) < val) - (val < T(0));
41}
42
52template<typename T>
53Eigen::ArrayXd
54cumSum(const T& x, const bool leading_zero = false)
55{
56 const size_t start = leading_zero ? 1 : 0;
57 Eigen::ArrayXd out(x.size() + start);
58
59 if (leading_zero) {
60 out(0) = 0.0;
61 }
62
63 std::partial_sum(x.begin(), x.end(), out.begin() + start);
64
65 return out;
66}
67
77template<typename T>
78T
79sigmoid(const T& x)
80{
81 return 1.0 / (1.0 + std::exp(-x));
82}
83
93template<typename T>
94T
95logit(const T& x)
96{
97 assert(x > 0 && x < 1 && "Input must be in (0, 1)");
98
99 return std::log(x) - std::log1p(-x);
100}
101
111template<typename T>
112T
113clamp(const T& x, const T& lo, const T& hi)
114{
115 return x < lo ? lo : x > hi ? hi : x;
116}
117
124Eigen::VectorXd
125logSumExp(const Eigen::MatrixXd& a);
126
135Eigen::MatrixXd
136softmax(const Eigen::MatrixXd& x);
137
138/*
139 * Computes the gradient of the loss with respect to \f(\beta\f).
140 *
141 * @tparam T The type of the input matrix.
142 * @param x The input matrix.
143 * @param active_set Indicies for active set
144 * @param beta0 Intercept
145 * @param beta Coefficients
146 * @param x_centers The vector of center values for each column of x.
147 * @param x_scales The vector of scale values for each column of x.
148 * @param jit_normalization Type of JIT normalization.
149 * @param intercept Whether to fit an intercept.
150 * @return The computed gradient vector.
151 */
152template<typename T>
153Eigen::MatrixXd
154linearPredictor(const T& x,
155 const std::vector<int>& active_set,
156 const Eigen::VectorXd& beta0,
157 const Eigen::VectorXd& beta,
158 const Eigen::VectorXd& x_centers,
159 const Eigen::VectorXd& x_scales,
160 const JitNormalization jit_normalization,
161 const bool intercept)
162{
163 int n = x.rows();
164 int p = x.cols();
165 int m = beta0.size();
166
167 Eigen::MatrixXd eta = Eigen::MatrixXd::Zero(n, m);
168
169#ifdef _OPENMP
170 bool large_problem = active_set.size() > 100 && n * active_set.size() > 1e7;
171#pragma omp parallel num_threads(Threads::get()) if (large_problem)
172#endif
173 {
174 Eigen::MatrixXd eta_local = Eigen::MatrixXd::Zero(n, m);
175
176#ifdef _OPENMP
177#pragma omp for nowait
178#endif
179 for (int i = 0; i < static_cast<int>(active_set.size()); ++i) {
180 int ind = active_set[i];
181 auto [k, j] = std::div(ind, p);
182
183 switch (jit_normalization) {
185 eta_local.col(k) += x.col(j) * beta(ind) / x_scales(j);
186 eta_local.col(k).array() -= beta(ind) * x_centers(j) / x_scales(j);
187 break;
188
190 eta_local.col(k) += x.col(j) * beta(ind);
191 eta_local.col(k).array() -= beta(ind) * x_centers(j);
192 break;
193
195 eta_local.col(k) += x.col(j) * beta(ind) / x_scales(j);
196 break;
197
199 eta_local.col(k) += x.col(j) * beta(ind);
200 break;
201 }
202 }
203
204#ifdef _OPENMP
205#pragma omp critical
206#endif
207 {
208 eta += eta_local;
209 }
210 }
211
212 if (intercept) {
213 eta.rowwise() += beta0.transpose();
214 }
215
216 return eta;
217}
218
233namespace detail {
234
235template<typename T, typename IndexAt>
236void
237updateGradientImpl(Eigen::VectorXd& gradient,
238 const T& x,
239 const Eigen::MatrixXd& residual,
240 const int active_size,
241 const IndexAt& index_at,
242 const Eigen::VectorXd& x_centers,
243 const Eigen::VectorXd& x_scales,
244 const Eigen::VectorXd& w,
245 const JitNormalization jit_normalization)
246{
247 const int n = x.rows();
248 const int p = x.cols();
249 const int m = residual.cols();
250
251 assert(gradient.size() == p * m &&
252 "Gradient matrix has incorrect dimensions");
253
254 Eigen::MatrixXd weighted_residual(n, m);
255 Eigen::ArrayXd wr_sums(m);
256
257#ifdef _OPENMP
258 bool large_problem =
259 active_size > 100 && static_cast<long long>(n) * active_size > 1e5;
260#pragma omp parallel for num_threads(Threads::get()) if (large_problem)
261#endif
262 for (int k = 0; k < m; ++k) {
263 weighted_residual.col(k) = residual.col(k).cwiseProduct(w);
264 wr_sums(k) = weighted_residual.col(k).sum();
265 }
266
267#ifdef _OPENMP
268#pragma omp parallel for num_threads(Threads::get()) if (large_problem)
269#endif
270 for (int i = 0; i < active_size; ++i) {
271 int ind = index_at(i);
272 auto [k, j] = std::div(ind, p);
273
274 switch (jit_normalization) {
276 gradient(ind) =
277 (x.col(j).dot(weighted_residual.col(k)) - x_centers(j) * wr_sums(k)) /
278 (x_scales(j) * n);
279 break;
281 gradient(ind) =
282 (x.col(j).dot(weighted_residual.col(k)) - x_centers(j) * wr_sums(k)) /
283 n;
284 break;
286 gradient(ind) =
287 x.col(j).dot(weighted_residual.col(k)) / (x_scales(j) * n);
288 break;
290 gradient(ind) = x.col(j).dot(weighted_residual.col(k)) / n;
291 break;
292 }
293 }
294}
295
296} // namespace detail
297
309template<typename T>
310void
311updateGradient(Eigen::VectorXd& gradient,
312 const T& x,
313 const Eigen::MatrixXd& residual,
314 const std::vector<int>& active_set,
315 const Eigen::VectorXd& x_centers,
316 const Eigen::VectorXd& x_scales,
317 const Eigen::VectorXd& w,
318 const JitNormalization jit_normalization)
319{
320 detail::updateGradientImpl(
321 gradient,
322 x,
323 residual,
324 static_cast<int>(active_set.size()),
325 [&active_set](int i) { return active_set[i]; },
326 x_centers,
327 x_scales,
328 w,
329 jit_normalization);
330}
331
337template<typename T>
338void
339updateGradient(Eigen::VectorXd& gradient,
340 const T& x,
341 const Eigen::MatrixXd& residual,
342 const Eigen::VectorXd& x_centers,
343 const Eigen::VectorXd& x_scales,
344 const Eigen::VectorXd& w,
345 const JitNormalization jit_normalization)
346{
347 detail::updateGradientImpl(
348 gradient,
349 x,
350 residual,
351 static_cast<int>(gradient.size()),
352 [](int i) { return i; },
353 x_centers,
354 x_scales,
355 w,
356 jit_normalization);
357}
358
359/*
360 * Computes the gradient of the loss with respect to \f(\beta\f).
361 *
362 * @tparam T The type of the input matrix.
363 * @param gradient The residual vector.
364 * @param x The input matrix.
365 * @param offset Gradient offset
366 * @param active_set Indices for the active_set
367 * @param x_centers The vector of center values for each column of x.
368 * @param x_scales The vector of scale values for each column of x.
369 * @param jit_normalization Type of JIT normalization
370 * just-in-time.
371 */
372namespace detail {
373
374template<typename T, typename IndexAt>
375void
376offsetGradientImpl(Eigen::VectorXd& gradient,
377 const T& x,
378 const Eigen::VectorXd& offset,
379 const int active_size,
380 const IndexAt& index_at,
381 const Eigen::VectorXd& x_centers,
382 const Eigen::VectorXd& x_scales,
383 const JitNormalization jit_normalization)
384{
385 const int n = x.rows();
386 const int p = x.cols();
387
388 for (int i = 0; i < active_size; ++i) {
389 int ind = index_at(i);
390 auto [k, j] = std::div(ind, p);
391
392 switch (jit_normalization) {
394 gradient(ind) -=
395 offset(k) * (x.col(j).sum() / n - x_centers(j)) / x_scales(j);
396 break;
398 gradient(ind) -= offset(k) * (x.col(j).sum() / n - x_centers(j));
399 break;
401 gradient(ind) -= offset(k) * x.col(j).sum() / (n * x_scales(j));
402 break;
404 gradient(ind) -= offset(k) * x.col(j).sum() / n;
405 break;
406 }
407 }
408}
409
410} // namespace detail
411
422template<typename T>
423void
424offsetGradient(Eigen::VectorXd& gradient,
425 const T& x,
426 const Eigen::VectorXd& offset,
427 const std::vector<int>& active_set,
428 const Eigen::VectorXd& x_centers,
429 const Eigen::VectorXd& x_scales,
430 const JitNormalization jit_normalization)
431{
432 detail::offsetGradientImpl(
433 gradient,
434 x,
435 offset,
436 static_cast<int>(active_set.size()),
437 [&active_set](int i) { return active_set[i]; },
438 x_centers,
439 x_scales,
440 jit_normalization);
441}
442
448template<typename T>
449void
450offsetGradient(Eigen::VectorXd& gradient,
451 const T& x,
452 const Eigen::VectorXd& offset,
453 const Eigen::VectorXd& x_centers,
454 const Eigen::VectorXd& x_scales,
455 const JitNormalization jit_normalization)
456{
457 detail::offsetGradientImpl(
458 gradient,
459 x,
460 offset,
461 static_cast<int>(gradient.size()),
462 [](int i) { return i; },
463 x_centers,
464 x_scales,
465 jit_normalization);
466}
467
476std::vector<int>
477setUnion(const std::vector<int>& a, const std::vector<int>& b);
478
489std::vector<int>
490setDiff(const std::vector<int>& a, const std::vector<int>& b);
491
503template<typename T>
504int
505whichMax(const T& x)
506{
507 return std::distance(x.begin(), std::max_element(x.begin(), x.end()));
508}
509
521template<typename T>
522int
523whichMin(const T& x)
524{
525 return std::distance(x.begin(), std::min_element(x.begin(), x.end()));
526}
527
541template<typename T, typename Comparator>
542int
543whichBest(const T& x, const Comparator& comp)
544{
545 if (x.size() == 0)
546 return -1;
547
548 return std::distance(x.begin(), std::max_element(x.begin(), x.end(), comp));
549}
550
565Eigen::ArrayXd
566geomSpace(const double start, const double end, const int n);
567
580template<typename T>
581Eigen::VectorXd
582l1Norms(const T& x)
583{
584 const int p = x.cols();
585
586 Eigen::VectorXd out(p);
587
588 for (int j = 0; j < p; ++j) {
589 out(j) = x.col(j).cwiseAbs().sum();
590 }
591
592 return out;
593}
594
605template<typename T>
606Eigen::VectorXd
607l2Norms(const Eigen::SparseMatrixBase<T>& x)
608{
609 const int p = x.cols();
610
611 Eigen::VectorXd out(p);
612
613 for (int j = 0; j < p; ++j) {
614 out(j) = x.col(j).norm();
615 }
616
617 return out;
618}
619
630template<typename T>
631Eigen::VectorXd
632l2Norms(const Eigen::MatrixBase<T>& x)
633{
634 return x.colwise().norm();
635}
636
648template<typename T>
649Eigen::VectorXd
650maxAbs(const Eigen::SparseMatrixBase<T>& x)
651{
652 const int p = x.cols();
653
654 Eigen::VectorXd out(p);
655
656 for (int j = 0; j < p; ++j) {
657 double x_j_maxabs = 0.0;
658
659 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
660 x_j_maxabs = std::max(x_j_maxabs, std::abs(it.value()));
661 }
662
663 out(j) = x_j_maxabs;
664 }
665
666 return out;
667}
668
681template<typename T>
682Eigen::VectorXd
683maxAbs(const Eigen::MatrixBase<T>& x)
684{
685 return x.cwiseAbs().colwise().maxCoeff();
686}
687
699template<typename T>
700Eigen::VectorXd
701means(const Eigen::SparseMatrixBase<T>& x)
702{
703 const int n = x.rows();
704 const int p = x.cols();
705
706 Eigen::VectorXd out(p);
707
708 for (int j = 0; j < p; ++j) {
709 out(j) = x.col(j).sum() / n;
710 }
711
712 return out;
713}
714
726template<typename T>
727Eigen::VectorXd
728means(const Eigen::MatrixBase<T>& x)
729{
730 return x.colwise().mean();
731}
732
745template<typename T>
746Eigen::VectorXd
747stdDevs(const Eigen::SparseMatrixBase<T>& x)
748{
749 const int n = x.rows();
750 const int p = x.cols();
751
752 Eigen::VectorXd x_means = means(x);
753 Eigen::VectorXd out(p);
754
755 for (int j = 0; j < p; ++j) {
756 double sum_sq_diff = 0.0;
757 const double mean = x_means(j);
758
759 // Process non-zero elements
760 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
761 double diff = it.value() - mean;
762 sum_sq_diff += diff * diff;
763 }
764
765 // Account for zeros
766 int nz_count = slope::nonZeros(x.col(j));
767 if (nz_count < n) {
768 sum_sq_diff += (n - nz_count) * mean * mean;
769 }
770
771 // Standard deviation is sqrt of the average of squared differences
772 out(j) = std::sqrt(sum_sq_diff / n);
773 }
774
775 return out;
776}
777
790template<typename T>
791Eigen::VectorXd
792stdDevs(const Eigen::MatrixBase<T>& x)
793{
794 int n = x.rows();
795 int p = x.cols();
796
797 Eigen::VectorXd x_means = means(x);
798 Eigen::VectorXd out(p);
799
800 for (int j = 0; j < p; ++j) {
801 out(j) = (x.col(j).array() - x_means(j)).matrix().norm();
802 }
803
804 out.array() /= std::sqrt(n);
805
806 return out;
807}
808
819template<typename T>
820Eigen::VectorXd
821ranges(const Eigen::SparseMatrixBase<T>& x)
822{
823 const int p = x.cols();
824
825 Eigen::VectorXd out(p);
826
827 for (int j = 0; j < p; ++j) {
828 double x_j_max = 0.0;
829 double x_j_min = 0.0;
830
831 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
832 x_j_max = std::max(x_j_max, it.value());
833 x_j_min = std::min(x_j_min, it.value());
834 }
835
836 out(j) = x_j_max - x_j_min;
837 }
838
839 return out;
840}
841
855template<typename T>
856Eigen::VectorXd
857ranges(const Eigen::MatrixBase<T>& x)
858{
859 return x.colwise().maxCoeff() - x.colwise().minCoeff();
860}
861
872template<typename T>
873Eigen::VectorXd
874mins(const Eigen::SparseMatrixBase<T>& x)
875{
876 const int p = x.cols();
877
878 Eigen::VectorXd out(p);
879
880 for (int j = 0; j < p; ++j) {
881 double x_j_min = 0.0;
882
883 for (typename T::InnerIterator it(x.derived(), j); it; ++it) {
884 x_j_min = std::min(x_j_min, it.value());
885 }
886
887 out(j) = x_j_min;
888 }
889
890 return out;
891}
892
906template<typename T>
907Eigen::VectorXd
908mins(const Eigen::MatrixBase<T>& x)
909{
910 return x.colwise().minCoeff();
911}
912
932template<typename T>
933Eigen::VectorXd
934clusterGradient(Eigen::VectorXd& beta,
935 Eigen::MatrixXd& residual,
936 Clusters& clusters,
937 const T& x,
938 const Eigen::MatrixXd& w,
939 const Eigen::VectorXd& x_centers,
940 const Eigen::VectorXd& x_scales,
941 const JitNormalization jit_normalization)
942{
943 using namespace Eigen;
944
945 const int n = x.rows();
946 const int p = x.cols();
947 const int n_clusters = clusters.size();
948
949 Eigen::VectorXd gradient = Eigen::VectorXd::Zero(n_clusters);
950
951 for (int j = 0; j < n_clusters; ++j) {
952 double c_old = clusters.coeff(j);
953
954 if (c_old == 0) {
955 gradient(j) = 0;
956 continue;
957 }
958
959 int cluster_size = clusters.cluster_size(j);
960 std::vector<int> s;
961 s.reserve(cluster_size);
962
963 for (auto c_it = clusters.cbegin(j); c_it != clusters.cend(j); ++c_it) {
964 int ind = *c_it;
965 double s_k = sign(beta(ind));
966 s.emplace_back(s_k);
967 }
968
969 double hess = 1;
970 double grad = 0;
971
972 if (cluster_size == 1) {
973 int k = *clusters.cbegin(j);
974 std::tie(grad, hess) = computeGradientAndHessian(
975 x, k, w, residual, x_centers, x_scales, s[0], jit_normalization, n);
976 } else {
977 std::tie(hess, grad) = computeClusterGradientAndHessian(
978 x, j, s, clusters, w, residual, x_centers, x_scales, jit_normalization);
979 }
980
981 gradient(j) = grad;
982 }
983
984 return gradient;
985}
986
987} // 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:701
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:89
T clamp(const T &x, const T &lo, const T &hi)
Definition math.h:113
Eigen::VectorXd l1Norms(const T &x)
Computes the L1 (Manhattan) norms for each column of a matrix.
Definition math.h:582
int sign(T val)
Returns the sign of a given value.
Definition math.h:38
int whichMin(const T &x)
Returns the index of the minimum element in a container.
Definition math.h:523
T sigmoid(const T &x)
Definition math.h:79
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:821
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:874
Eigen::ArrayXd cumSum(const T &x, const bool leading_zero=false)
Definition math.h:54
int whichBest(const T &x, const Comparator &comp)
Returns the index of the minimum element in a container.
Definition math.h:543
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
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:424
T logit(const T &x)
Definition math.h:95
Eigen::VectorXd stdDevs(const Eigen::SparseMatrixBase< T > &x)
Computes the standard deviation for each column of a matrix.
Definition math.h:747
Eigen::VectorXd l2Norms(const Eigen::SparseMatrixBase< T > &x)
Computes the L2 (Euclidean) norms for each column of a sparse matrix.
Definition math.h:607
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
Eigen::VectorXd maxAbs(const Eigen::SparseMatrixBase< T > &x)
Computes the maximum absolute value for each column of a matrix.
Definition math.h:650
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:275
int whichMax(const T &x)
Returns the index of the maximum element in a container.
Definition math.h:505
Eigen::Index nonZeros(const Eigen::SparseMatrixBase< Derived > &x)
Definition utils.h:34
Thread management for parallel computations.
Various utility functions.