22template<
typename W,
typename R>
24weightedResidualSum(
const Eigen::MatrixBase<W>& weights,
25 const Eigen::MatrixBase<R>& residual)
27 if (!(weights.array() == 0.0).any()) {
28 return weights.dot(residual);
31 for (Eigen::Index i = 0; i < weights.size(); ++i) {
32 if (weights(i) != 0.0) {
33 value += weights(i) * residual(i);
39class SparseClusterWorkspace
42 SparseClusterWorkspace() =
default;
44 SparseClusterWorkspace(
const int n,
const int m) { resize(n, m); }
46 void resize(
const int n,
const int m)
48 if (values.rows() != n || values.cols() != m) {
49 values = Eigen::MatrixXd::Zero(n, m);
50 active.assign(values.size(),
false);
55 void add(
const int row,
const int col,
const double value)
57 const Eigen::Index index = row + values.rows() * col;
60 touched.emplace_back(index);
62 values.data()[index] += value;
67 for (
const Eigen::Index index : touched) {
68 values.data()[index] = 0.0;
69 active[index] =
false;
74 Eigen::MatrixXd values;
75 std::vector<unsigned char> active;
76 std::vector<Eigen::Index> touched;
105std::pair<double, double>
108 const Eigen::MatrixXd& w,
109 const Eigen::MatrixXd& residual,
110 const Eigen::VectorXd& x_centers,
111 const Eigen::VectorXd& x_scales,
115 const Eigen::VectorXd& weight_sums)
117 double gradient = 0.0;
118 double hessian = 0.0;
122 auto [k, j] = std::div(ind, p);
124 if ((w.col(k).array() == 0.0).any()) {
129 for (
int i = 0; i < n; ++i) {
130 if (w(i, k) == 0.0) {
133 double value = x(i, j) - (center ? x_centers(j) : 0.0);
135 value /= x_scales(j);
137 gradient += s * value * w(i, k) * residual(i, k);
138 hessian += value * value * w(i, k);
140 return { gradient / n, hessian / n };
144 Eigen::VectorXd residual_v = residual.col(k);
145 Eigen::VectorXd w_v = w.col(k);
147 switch (jit_normalization) {
150 (x.col(j).cwiseProduct(w_v).dot(residual_v) -
151 w_v.dot(residual_v) * x_centers(j)) /
154 (x.col(j).cwiseAbs2().dot(w_v) - 2 * x_centers(j) * x.col(j).dot(w_v) +
155 std::pow(x_centers(j), 2) * weight_sums(k)) /
156 (std::pow(x_scales(j), 2) * n);
161 (x.col(j).cwiseProduct(w_v).dot(residual_v) -
162 w_v.dot(residual_v) * x_centers(j)) /
165 (x.col(j).cwiseAbs2().dot(w_v) - 2 * x_centers(j) * x.col(j).dot(w_v) +
166 std::pow(x_centers(j), 2) * weight_sums(k)) /
172 s * (x.col(j).cwiseProduct(w_v).dot(residual_v)) / (n * x_scales(j));
173 hessian = x.col(j).cwiseAbs2().dot(w_v) / (std::pow(x_scales(j), 2) * n);
177 gradient = s * (x.col(j).cwiseProduct(w_v).dot(residual_v)) / n;
178 hessian = x.col(j).cwiseAbs2().dot(w_v) / n;
182 return { gradient, hessian };
186std::pair<double, double>
189 const Eigen::MatrixXd& w,
190 const Eigen::MatrixXd& residual,
191 const Eigen::VectorXd& x_centers,
192 const Eigen::VectorXd& x_scales,
197 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
211std::pair<double, double>
214 const Eigen::MatrixXd& w,
215 const Eigen::MatrixXd& residual,
216 const Eigen::VectorXd& x_centers,
217 const Eigen::VectorXd& x_scales,
221 const Eigen::VectorXd& weight_sums)
223 const int p = x.cols();
224 auto [k, j] = std::div(ind, p);
226 double weighted_x_residual_sum = 0.0;
227 double weighted_x_sum = 0.0;
228 double weighted_x_squared_sum = 0.0;
230 for (
typename T::InnerIterator it(x.derived(), j); it; ++it) {
231 const int i = it.row();
232 const double value = it.value();
233 const double weight = w(i, k);
239 weighted_x_residual_sum += value * weight * residual(i, k);
240 weighted_x_sum += value * weight;
241 weighted_x_squared_sum += value * value * weight;
248 const double offset = center ? x_centers(j) : 0.0;
249 const double feature_scale = scale ? x_scales(j) : 1.0;
252 weighted_x_residual_sum -=
253 offset * detail::weightedResidualSum(w.col(k), residual.col(k));
254 weighted_x_squared_sum +=
255 offset * offset * weight_sums(k) - 2.0 * offset * weighted_x_sum;
258 const double gradient = s * weighted_x_residual_sum / (n * feature_scale);
259 const double hessian =
260 weighted_x_squared_sum / (n * feature_scale * feature_scale);
262 return { gradient, hessian };
266std::pair<double, double>
269 const Eigen::MatrixXd& w,
270 const Eigen::MatrixXd& residual,
271 const Eigen::VectorXd& x_centers,
272 const Eigen::VectorXd& x_scales,
277 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
314std::pair<double, double>
317 const std::vector<int>& s,
319 const Eigen::MatrixXd& w,
320 const Eigen::MatrixXd& residual,
321 const Eigen::VectorXd& x_centers,
322 const Eigen::VectorXd& x_scales,
327 int m = residual.cols();
329 Eigen::MatrixXd x_s = Eigen::MatrixXd::Zero(n, m);
331 auto s_it = s.cbegin();
332 auto c_it = clusters.
cbegin(c_ind);
334 for (; c_it != clusters.
cend(c_ind); ++c_it, ++s_it) {
336 auto [k, j] = std::div(ind, p);
339 switch (jit_normalization) {
341 x_s.col(k) += x.col(j) * (s / x_scales(j));
342 x_s.col(k).array() -= x_centers(j) * s / x_scales(j);
346 x_s.col(k) += x.col(j) * s;
347 x_s.col(k).array() -= x_centers(j) * s;
351 x_s.col(k) += x.col(j) * (s / x_scales(j));
355 x_s.col(k) += x.col(j) * s;
363 for (
int k = 0; k < m; ++k) {
364 if ((w.col(k).array() == 0.0).any()) {
365 for (
int i = 0; i < n; ++i) {
366 if (w(i, k) != 0.0) {
367 hess += x_s(i, k) * x_s(i, k) * w(i, k) / n;
368 grad += x_s(i, k) * w(i, k) * residual(i, k) / n;
372 hess += x_s.col(k).cwiseAbs2().dot(w.col(k)) / n;
373 grad += x_s.col(k).cwiseProduct(w.col(k)).dot(residual.col(k)) / n;
377 return { hess, grad };
405std::pair<double, double>
406computeSparseClusterGradientAndHessian(
const Eigen::SparseMatrixBase<T>& x,
408 const std::vector<int>& s,
409 const Clusters& clusters,
410 const Eigen::MatrixXd& w,
411 const Eigen::MatrixXd& residual,
412 const Eigen::VectorXd& weight_sums,
413 const Eigen::VectorXd& x_centers,
414 const Eigen::VectorXd& x_scales,
416 SparseClusterWorkspace& workspace)
420 int m = residual.cols();
422 workspace.resize(n, m);
424 Eigen::ArrayXd offset = Eigen::ArrayXd::Zero(m);
426 auto s_it = s.cbegin();
427 auto c_it = clusters.cbegin(c_ind);
429 for (; c_it != clusters.cend(c_ind); ++c_it, ++s_it) {
431 auto [k, j] = std::div(ind, p);
432 double s_ind = *s_it;
434 switch (jit_normalization) {
436 offset(k) += x_centers(j) * s_ind;
439 offset(k) += x_centers(j) * s_ind / x_scales(j);
447 double scale = s_ind;
450 scale /= x_scales(j);
453 for (
typename T::InnerIterator it(x.derived(), j); it; ++it) {
454 if (w(it.row(), k) != 0.0) {
455 workspace.add(it.row(), k, it.value() * scale);
462 Eigen::ArrayXd weighted_sum = Eigen::ArrayXd::Zero(m);
464 for (
const Eigen::Index index : workspace.touched) {
465 const int k = index / n;
466 const int i = index - k * n;
467 const double value = workspace.values.data()[index];
468 const double weight = w(i, k);
474 hess += value * value * weight;
475 grad += value * weight * residual(i, k);
476 weighted_sum(k) += value * weight;
481 for (
int k = 0; k < m; ++k) {
482 hess += -2 * offset(k) * weighted_sum(k) +
483 std::pow(offset(k), 2) * weight_sums(k);
484 grad -= offset(k) * weightedResidualSum(w.col(k), residual.col(k));
490 return { hess / n, grad / n };
494std::pair<double, double>
495computeClusterGradientAndHessianWithWorkspace(
496 const Eigen::MatrixBase<T>& x,
498 const std::vector<int>& s,
499 const Clusters& clusters,
500 const Eigen::MatrixXd& w,
501 const Eigen::MatrixXd& residual,
502 const Eigen::VectorXd&,
503 const Eigen::VectorXd& x_centers,
504 const Eigen::VectorXd& x_scales,
506 SparseClusterWorkspace&)
509 x, c_ind, s, clusters, w, residual, x_centers, x_scales, jit_normalization);
513std::pair<double, double>
514computeClusterGradientAndHessianWithWorkspace(
515 const Eigen::SparseMatrixBase<T>& x,
517 const std::vector<int>& s,
518 const Clusters& clusters,
519 const Eigen::MatrixXd& w,
520 const Eigen::MatrixXd& residual,
521 const Eigen::VectorXd& weight_sums,
522 const Eigen::VectorXd& x_centers,
523 const Eigen::VectorXd& x_scales,
525 SparseClusterWorkspace& workspace)
527 return computeSparseClusterGradientAndHessian(x,
543std::pair<double, double>
546 const std::vector<int>& s,
548 const Eigen::MatrixXd& w,
549 const Eigen::MatrixXd& residual,
550 const Eigen::VectorXd& x_centers,
551 const Eigen::VectorXd& x_scales,
554 detail::SparseClusterWorkspace workspace(x.rows(), residual.cols());
555 const Eigen::VectorXd weight_sums = w.colwise().sum().transpose();
556 return detail::computeSparseClusterGradientAndHessian(x,
602 Eigen::VectorXd& beta,
603 Eigen::MatrixXd& residual,
605 const Eigen::ArrayXd& lambda_cumsum,
607 const Eigen::MatrixXd& w,
608 const Eigen::VectorXd& weight_sums,
609 const Eigen::VectorXd& x_centers,
610 const Eigen::VectorXd& x_scales,
611 const bool intercept,
613 const bool update_clusters,
615 const std::string& cd_type =
"cyclical")
617 using namespace Eigen;
619 const int n = x.rows();
620 const int p = x.cols();
621 const int m = residual.cols();
623 double max_abs_gradient = 0;
624 detail::SparseClusterWorkspace sparse_workspace;
627 std::vector<int> indices;
628 indices.reserve(clusters.
size());
629 for (
int i = 0; i < clusters.
size(); ++i) {
630 if (clusters.
coeff(i) != 0) {
631 indices.push_back(i);
635 if (cd_type ==
"permuted") {
636 std::shuffle(indices.begin(), indices.end(), rng);
639 for (
int c_ind : indices) {
641 if (c_ind >= clusters.
size()) {
645 double c_old = clusters.
coeff(c_ind);
655 s.reserve(cluster_size);
657 for (
auto c_it = clusters.
cbegin(c_ind); c_it != clusters.
cend(c_ind);
660 assert(ind >= 0 && ind < beta.size() &&
"Invalid index in cluster");
661 double s_ind =
sign(beta(ind));
662 s.emplace_back(s_ind);
669 if (cluster_size == 1) {
670 int ind = *clusters.
cbegin(c_ind);
682 std::tie(hess, grad) =
683 detail::computeClusterGradientAndHessianWithWorkspace(x,
696 max_abs_gradient = std::max(max_abs_gradient, std::abs(grad));
701 const double gamma = hess * c_old - grad;
702 std::tie(c_tilde, new_index) =
705 assert(c_tilde == 0 || new_index < clusters.
size());
706 assert(new_index >= 0 && new_index <= clusters.
size());
708 double c_diff = c_old - c_tilde;
711 auto s_it = s.cbegin();
712 auto c_it = clusters.
cbegin(c_ind);
713 for (; c_it != clusters.
cend(c_ind); ++c_it, ++s_it) {
715 auto [k, j] = std::div(ind, p);
716 double s_ind = *s_it;
719 beta(ind) = c_tilde * s_ind;
722 switch (jit_normalization) {
724 residual.col(k) -= x.col(j) * (s_ind * c_diff / x_scales(j));
725 residual.col(k).array() +=
726 x_centers(j) * s_ind * c_diff / x_scales(j);
730 residual.col(k) -= x.col(j) * (s_ind * c_diff);
731 residual.col(k).array() += x_centers(j) * s_ind * c_diff;
735 residual.col(k) -= x.col(j) * (s_ind * c_diff / x_scales(j));
739 residual.col(k) -= x.col(j) * (s_ind * c_diff);
745 if (update_clusters) {
746 clusters.
update(c_ind, new_index, std::abs(c_tilde));
748 clusters.
setCoeff(c_ind, std::abs(c_tilde));
753 for (
int k = 0; k < residual.cols(); ++k) {
754 double beta0_update =
755 detail::weightedResidualSum(w.col(k), residual.col(k)) / n;
756 residual.col(k).array() -= beta0_update;
757 beta0(k) -= beta0_update;
761 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.
The declaration of the Clusters class.
Eigen compatibility layer for version differences.
Mathematical support functions for the slope package.
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.