9#include "../clusters.h"
10#include "../eigen_compat.h"
22class SparseClusterWorkspace
25 SparseClusterWorkspace() =
default;
27 SparseClusterWorkspace(
const int n,
const int m) { resize(n, m); }
29 void resize(
const int n,
const int m)
31 if (values.rows() != n || values.cols() != m) {
32 values = Eigen::MatrixXd::Zero(n, m);
33 active.assign(values.size(),
false);
38 void add(
const int row,
const int col,
const double value)
40 const Eigen::Index index = row + values.rows() * col;
43 touched.emplace_back(index);
45 values.data()[index] += value;
50 for (
const Eigen::Index index : touched) {
51 values.data()[index] = 0.0;
52 active[index] =
false;
57 Eigen::MatrixXd values;
58 std::vector<unsigned char> active;
59 std::vector<Eigen::Index> touched;
88std::pair<double, double>
91 const Eigen::MatrixXd& w,
92 const Eigen::MatrixXd& residual,
93 const Eigen::VectorXd& x_centers,
94 const Eigen::VectorXd& x_scales,
98 const Eigen::VectorXd& weight_sums)
100 double gradient = 0.0;
101 double hessian = 0.0;
105 auto [k, j] = std::div(ind, p);
108 Eigen::VectorXd residual_v = residual.col(k);
109 Eigen::VectorXd w_v = w.col(k);
111 switch (jit_normalization) {
114 (x.col(j).cwiseProduct(w_v).dot(residual_v) -
115 w_v.dot(residual_v) * x_centers(j)) /
118 (x.col(j).cwiseAbs2().dot(w_v) - 2 * x_centers(j) * x.col(j).dot(w_v) +
119 std::pow(x_centers(j), 2) * weight_sums(k)) /
120 (std::pow(x_scales(j), 2) * n);
125 (x.col(j).cwiseProduct(w_v).dot(residual_v) -
126 w_v.dot(residual_v) * x_centers(j)) /
129 (x.col(j).cwiseAbs2().dot(w_v) - 2 * x_centers(j) * x.col(j).dot(w_v) +
130 std::pow(x_centers(j), 2) * weight_sums(k)) /
136 s * (x.col(j).cwiseProduct(w_v).dot(residual_v)) / (n * x_scales(j));
137 hessian = x.col(j).cwiseAbs2().dot(w_v) / (std::pow(x_scales(j), 2) * n);
141 gradient = s * (x.col(j).cwiseProduct(w_v).dot(residual_v)) / n;
142 hessian = x.col(j).cwiseAbs2().dot(w_v) / n;
146 return { gradient, hessian };
150std::pair<double, double>
153 const Eigen::MatrixXd& w,
154 const Eigen::MatrixXd& residual,
155 const Eigen::VectorXd& x_centers,
156 const Eigen::VectorXd& x_scales,
161 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
175std::pair<double, double>
178 const Eigen::MatrixXd& w,
179 const Eigen::MatrixXd& residual,
180 const Eigen::VectorXd& x_centers,
181 const Eigen::VectorXd& x_scales,
185 const Eigen::VectorXd& weight_sums)
187 const int p = x.cols();
188 auto [k, j] = std::div(ind, p);
190 double weighted_x_residual_sum = 0.0;
191 double weighted_x_sum = 0.0;
192 double weighted_x_squared_sum = 0.0;
194 for (
typename T::InnerIterator it(x.derived(), j); it; ++it) {
195 const int i = it.row();
196 const double value = it.value();
197 const double weight = w(i, k);
199 weighted_x_residual_sum += value * weight * residual(i, k);
200 weighted_x_sum += value * weight;
201 weighted_x_squared_sum += value * value * weight;
208 const double offset = center ? x_centers(j) : 0.0;
209 const double feature_scale = scale ? x_scales(j) : 1.0;
212 weighted_x_residual_sum -=
213 offset * w.col(k).cwiseProduct(residual.col(k)).sum();
214 weighted_x_squared_sum +=
215 offset * offset * weight_sums(k) - 2.0 * offset * weighted_x_sum;
218 const double gradient = s * weighted_x_residual_sum / (n * feature_scale);
219 const double hessian =
220 weighted_x_squared_sum / (n * feature_scale * feature_scale);
222 return { gradient, hessian };
226std::pair<double, double>
229 const Eigen::MatrixXd& w,
230 const Eigen::MatrixXd& residual,
231 const Eigen::VectorXd& x_centers,
232 const Eigen::VectorXd& x_scales,
237 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
274std::pair<double, double>
277 const std::vector<int>& s,
279 const Eigen::MatrixXd& w,
280 const Eigen::MatrixXd& residual,
281 const Eigen::VectorXd& x_centers,
282 const Eigen::VectorXd& x_scales,
287 int m = residual.cols();
289 Eigen::MatrixXd x_s = Eigen::MatrixXd::Zero(n, m);
291 auto s_it = s.cbegin();
292 auto c_it = clusters.
cbegin(c_ind);
294 for (; c_it != clusters.
cend(c_ind); ++c_it, ++s_it) {
296 auto [k, j] = std::div(ind, p);
299 switch (jit_normalization) {
301 x_s.col(k) += x.col(j) * (s / x_scales(j));
302 x_s.col(k).array() -= x_centers(j) * s / x_scales(j);
306 x_s.col(k) += x.col(j) * s;
307 x_s.col(k).array() -= x_centers(j) * s;
311 x_s.col(k) += x.col(j) * (s / x_scales(j));
315 x_s.col(k) += x.col(j) * s;
323 for (
int k = 0; k < m; ++k) {
324 hess += x_s.col(k).cwiseAbs2().dot(w.col(k)) / n;
325 grad += x_s.col(k).cwiseProduct(w.col(k)).dot(residual.col(k)) / n;
328 return { hess, grad };
356std::pair<double, double>
357computeSparseClusterGradientAndHessian(
const Eigen::SparseMatrixBase<T>& x,
359 const std::vector<int>& s,
360 const Clusters& clusters,
361 const Eigen::MatrixXd& w,
362 const Eigen::MatrixXd& residual,
363 const Eigen::VectorXd& weight_sums,
364 const Eigen::VectorXd& x_centers,
365 const Eigen::VectorXd& x_scales,
367 SparseClusterWorkspace& workspace)
371 int m = residual.cols();
373 workspace.resize(n, m);
375 Eigen::ArrayXd offset = Eigen::ArrayXd::Zero(m);
377 auto s_it = s.cbegin();
378 auto c_it = clusters.cbegin(c_ind);
380 for (; c_it != clusters.cend(c_ind); ++c_it, ++s_it) {
382 auto [k, j] = std::div(ind, p);
383 double s_ind = *s_it;
385 switch (jit_normalization) {
387 offset(k) += x_centers(j) * s_ind;
390 offset(k) += x_centers(j) * s_ind / x_scales(j);
398 double scale = s_ind;
401 scale /= x_scales(j);
404 for (
typename T::InnerIterator it(x.derived(), j); it; ++it) {
405 workspace.add(it.row(), k, it.value() * scale);
411 Eigen::ArrayXd weighted_sum = Eigen::ArrayXd::Zero(m);
413 for (
const Eigen::Index index : workspace.touched) {
414 const int k = index / n;
415 const int i = index - k * n;
416 const double value = workspace.values.data()[index];
417 const double weight = w(i, k);
419 hess += value * value * weight;
420 grad += value * weight * residual(i, k);
421 weighted_sum(k) += value * weight;
426 for (
int k = 0; k < m; ++k) {
427 hess += -2 * offset(k) * weighted_sum(k) +
428 std::pow(offset(k), 2) * weight_sums(k);
429 grad -= offset(k) * w.col(k).cwiseProduct(residual.col(k)).sum();
435 return { hess / n, grad / n };
439std::pair<double, double>
440computeClusterGradientAndHessianWithWorkspace(
441 const Eigen::MatrixBase<T>& x,
443 const std::vector<int>& s,
444 const Clusters& clusters,
445 const Eigen::MatrixXd& w,
446 const Eigen::MatrixXd& residual,
447 const Eigen::VectorXd&,
448 const Eigen::VectorXd& x_centers,
449 const Eigen::VectorXd& x_scales,
451 SparseClusterWorkspace&)
454 x, c_ind, s, clusters, w, residual, x_centers, x_scales, jit_normalization);
458std::pair<double, double>
459computeClusterGradientAndHessianWithWorkspace(
460 const Eigen::SparseMatrixBase<T>& x,
462 const std::vector<int>& s,
463 const Clusters& clusters,
464 const Eigen::MatrixXd& w,
465 const Eigen::MatrixXd& residual,
466 const Eigen::VectorXd& weight_sums,
467 const Eigen::VectorXd& x_centers,
468 const Eigen::VectorXd& x_scales,
470 SparseClusterWorkspace& workspace)
472 return computeSparseClusterGradientAndHessian(x,
488std::pair<double, double>
491 const std::vector<int>& s,
492 const Clusters& clusters,
493 const Eigen::MatrixXd& w,
494 const Eigen::MatrixXd& residual,
495 const Eigen::VectorXd& x_centers,
496 const Eigen::VectorXd& x_scales,
499 detail::SparseClusterWorkspace workspace(x.rows(), residual.cols());
500 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
501 return detail::computeSparseClusterGradientAndHessian(x,
547 Eigen::VectorXd& beta,
548 Eigen::MatrixXd& residual,
550 const Eigen::ArrayXd& lambda_cumsum,
552 const Eigen::MatrixXd& w,
553 const Eigen::VectorXd& weight_sums,
554 const Eigen::VectorXd& x_centers,
555 const Eigen::VectorXd& x_scales,
556 const bool intercept,
558 const bool update_clusters,
560 const std::string& cd_type =
"cyclical")
562 using namespace Eigen;
564 const int n = x.rows();
565 const int p = x.cols();
566 const int m = residual.cols();
568 double max_abs_gradient = 0;
569 detail::SparseClusterWorkspace sparse_workspace;
572 std::vector<int> indices;
573 indices.reserve(clusters.
size());
574 for (
int i = 0; i < clusters.
size(); ++i) {
575 if (clusters.
coeff(i) != 0) {
576 indices.push_back(i);
580 if (cd_type ==
"permuted") {
581 std::shuffle(indices.begin(), indices.end(), rng);
584 for (
int c_ind : indices) {
586 if (c_ind >= clusters.
size()) {
590 double c_old = clusters.
coeff(c_ind);
600 s.reserve(cluster_size);
602 for (
auto c_it = clusters.
cbegin(c_ind); c_it != clusters.
cend(c_ind);
605 assert(ind >= 0 && ind < beta.size() &&
"Invalid index in cluster");
606 double s_ind =
sign(beta(ind));
607 s.emplace_back(s_ind);
614 if (cluster_size == 1) {
615 int ind = *clusters.
cbegin(c_ind);
627 std::tie(hess, grad) =
628 detail::computeClusterGradientAndHessianWithWorkspace(x,
641 max_abs_gradient = std::max(max_abs_gradient, std::abs(grad));
646 const double gamma = hess * c_old - grad;
647 std::tie(c_tilde, new_index) =
650 assert(c_tilde == 0 || new_index < clusters.
size());
651 assert(new_index >= 0 && new_index <= clusters.
size());
653 double c_diff = c_old - c_tilde;
656 auto s_it = s.cbegin();
657 auto c_it = clusters.
cbegin(c_ind);
658 for (; c_it != clusters.
cend(c_ind); ++c_it, ++s_it) {
660 auto [k, j] = std::div(ind, p);
661 double s_ind = *s_it;
664 beta(ind) = c_tilde * s_ind;
667 switch (jit_normalization) {
669 residual.col(k) -= x.col(j) * (s_ind * c_diff / x_scales(j));
670 residual.col(k).array() +=
671 x_centers(j) * s_ind * c_diff / x_scales(j);
675 residual.col(k) -= x.col(j) * (s_ind * c_diff);
676 residual.col(k).array() += x_centers(j) * s_ind * c_diff;
680 residual.col(k) -= x.col(j) * (s_ind * c_diff / x_scales(j));
684 residual.col(k) -= x.col(j) * (s_ind * c_diff);
690 if (update_clusters) {
691 clusters.
update(c_ind, new_index, std::abs(c_tilde));
693 clusters.
setCoeff(c_ind, std::abs(c_tilde));
698 for (
int k = 0; k < residual.cols(); ++k) {
699 double beta0_update = residual.col(k).dot(w.col(k)) / n;
700 residual.col(k).array() -= beta0_update;
701 beta0(k) -= beta0_update;
705 return max_abs_gradient;
Representation of the nonzero clusters in SLOPE.
void update(const int old_index, const int new_index, const double c_new)
Updates the cluster structure when an index is changed.
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.
int cluster_size(const int i) const
Returns the size of the cluster with the given index.
void setCoeff(const int i, const double x)
Sets the coefficient 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.
Namespace containing SLOPE regression implementation.
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")
std::tuple< double, int > slopeThreshold(const double x, const int j, const Eigen::ArrayXd &lambda_cumsum, const Clusters &clusters)
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)
int sign(T val)
Returns the sign of a given value.
JitNormalization
Enums to control predictor standardization behavior.
@ None
No JIT normalization.
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)
The declaration of the slopeThreshold function.