slope 6.6.0
Loading...
Searching...
No Matches
diagnostics.h
Go to the documentation of this file.
1
5
6#pragma once
7
8#include "jit_normalization.h"
9#include "losses/loss.h"
10#include "math.h"
11#include "observation_weights.h"
12#include "ols.h"
13#include "sorted_l1_norm.h"
14#include <Eigen/Dense>
15#include <Eigen/SparseCore>
16#include <algorithm>
17#include <memory>
18
19namespace slope {
20
21namespace detail {
22
23template<typename T>
24std::pair<double, Eigen::VectorXd>
25fitWeightedOls(const Eigen::MatrixBase<T>& x,
26 const Eigen::VectorXd& y,
27 const bool intercept,
28 const Eigen::VectorXd& weights)
29{
30 const Eigen::VectorXd root_weights = weights.array().sqrt();
31 Eigen::MatrixXd design =
32 Eigen::MatrixXd::Zero(x.rows(), x.cols() + static_cast<int>(intercept));
33 Eigen::VectorXd response = Eigen::VectorXd::Zero(y.size());
34 for (Eigen::Index i = 0; i < x.rows(); ++i) {
35 if (weights(i) > 0.0) {
36 design.row(i).head(x.cols()) = x.row(i) * root_weights(i);
37 response(i) = y(i) * root_weights(i);
38 }
39 }
40 if (intercept) {
41 design.col(x.cols()) = root_weights;
42 }
43 auto fit = fitOls(design, response, false);
44 if (!intercept) {
45 return fit;
46 }
47 const double beta0 = fit.second(x.cols());
48 return { beta0, fit.second.head(x.cols()) };
49}
50
51template<typename T>
52std::pair<double, Eigen::VectorXd>
53fitWeightedOls(const Eigen::SparseMatrixBase<T>& x,
54 const Eigen::VectorXd& y,
55 const bool intercept,
56 const Eigen::VectorXd& weights)
57{
58 const Eigen::VectorXd root_weights = weights.array().sqrt();
59 Eigen::SparseMatrix<double> weighted_x = x;
60 for (int k = 0; k < weighted_x.outerSize(); ++k) {
61 for (Eigen::SparseMatrix<double>::InnerIterator it(weighted_x, k); it;
62 ++it) {
63 it.valueRef() =
64 weights(it.row()) > 0.0 ? it.value() * root_weights(it.row()) : 0.0;
65 }
66 }
67 Eigen::VectorXd response = Eigen::VectorXd::Zero(y.size());
68 for (Eigen::Index i = 0; i < y.size(); ++i) {
69 if (weights(i) > 0.0) {
70 response(i) = y(i) * root_weights(i);
71 }
72 }
73 Eigen::SparseMatrix<double> design(x.rows(),
74 x.cols() + static_cast<int>(intercept));
75 design.leftCols(x.cols()) = weighted_x;
76 if (intercept) {
77 Eigen::SparseMatrix<double> intercept_column = root_weights.sparseView();
78 design.rightCols(1) = intercept_column;
79 }
80 auto fit = fitOls(design, response, false);
81 if (!intercept) {
82 return fit;
83 }
84 const double beta0 = fit.second(x.cols());
85 return { beta0, fit.second.head(x.cols()) };
86}
87
88template<typename T>
89Eigen::MatrixXd
90quadraticOlsDualPointFromDesign(
91 const T& x,
92 const Eigen::MatrixXd& y,
93 const bool intercept,
94 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
95{
96 const Eigen::VectorXd weights =
97 normalizeObservationWeights(observation_weights, y.rows());
98 Eigen::MatrixXd theta(y.rows(), y.cols());
99
100 for (Eigen::Index k = 0; k < y.cols(); ++k) {
101 auto fit =
102 uniformObservationWeights(weights)
103 ? fitOls(x, Eigen::VectorXd(y.col(k)), intercept)
104 : fitWeightedOls(x, Eigen::VectorXd(y.col(k)), intercept, weights);
105 const auto& [beta0, beta] = fit;
106 theta.col(k) = x * beta - y.col(k);
107 if (intercept) {
108 theta.col(k).array() += beta0;
109 const double center = weightedMean(theta.col(k), weights)(0);
110 theta.col(k).array() -= center;
111 }
112 }
113
114 for (Eigen::Index i = 0; i < y.rows(); ++i) {
115 if (weights(i) == 0.0) {
116 theta.row(i).setZero();
117 }
118 }
119
120 return theta;
121}
122
123template<typename T>
124Eigen::MatrixXd
125quadraticOlsDualPoint(
126 const Eigen::MatrixBase<T>& x,
127 const Eigen::MatrixXd& y,
128 const Eigen::VectorXd& x_centers,
129 const Eigen::VectorXd& x_scales,
130 const JitNormalization jit_normalization,
131 const bool intercept,
132 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
133{
134 const Eigen::VectorXd weights =
135 normalizeObservationWeights(observation_weights, y.rows());
136 Eigen::MatrixXd x_effective = x;
137 const bool center = jit_normalization == JitNormalization::Center ||
138 jit_normalization == JitNormalization::Both;
139 const bool scale = jit_normalization == JitNormalization::Scale ||
140 jit_normalization == JitNormalization::Both;
141
142 for (Eigen::Index i = 0; i < weights.size(); ++i) {
143 if (weights(i) == 0.0) {
144 x_effective.row(i).setZero();
145 }
146 }
147 if (center && !intercept) {
148 x_effective.rowwise() -= x_centers.transpose();
149 }
150 // Mask again after centering so ignored rows cannot overflow during scaling.
151 for (Eigen::Index i = 0; i < weights.size(); ++i) {
152 if (weights(i) == 0.0) {
153 x_effective.row(i).setZero();
154 }
155 }
156 if (scale) {
157 x_effective.array().rowwise() /= x_scales.transpose().array();
158 }
159
160 return quadraticOlsDualPointFromDesign(
161 x_effective, y, intercept, observation_weights);
162}
163
164template<typename T>
165Eigen::MatrixXd
166quadraticOlsDualPoint(
167 const Eigen::SparseMatrixBase<T>& x,
168 const Eigen::MatrixXd& y,
169 const Eigen::VectorXd& x_centers,
170 const Eigen::VectorXd& x_scales,
171 const JitNormalization jit_normalization,
172 const bool intercept,
173 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
174{
175 const Eigen::VectorXd weights =
176 normalizeObservationWeights(observation_weights, y.rows());
177 const bool center = jit_normalization == JitNormalization::Center ||
178 jit_normalization == JitNormalization::Both;
179 const bool scale = jit_normalization == JitNormalization::Scale ||
180 jit_normalization == JitNormalization::Both;
181
182 Eigen::SparseMatrix<double> x_effective = x;
183 for (int k = 0; k < x_effective.outerSize(); ++k) {
184 for (Eigen::SparseMatrix<double>::InnerIterator it(x_effective, k); it;
185 ++it) {
186 if (weights(it.row()) == 0.0) {
187 it.valueRef() = 0.0;
188 } else if (scale) {
189 it.valueRef() /= x_scales(it.col());
190 }
191 }
192 }
193
194 if (!center || intercept) {
195 return quadraticOlsDualPointFromDesign(
196 x_effective, y, intercept, observation_weights);
197 }
198
199 Eigen::VectorXd effective_centers = x_centers;
200 if (scale) {
201 effective_centers.array() /= x_scales.array();
202 }
203
204 if (!uniformObservationWeights(observation_weights)) {
205 // Without an intercept, explicit centering changes the constrained design.
206 // Fit that design directly rather than adding a free intercept to sparse
207 // QR.
208 Eigen::MatrixXd centered =
209 Eigen::MatrixXd(x_effective).rowwise() - effective_centers.transpose();
210 for (Eigen::Index i = 0; i < weights.size(); ++i) {
211 if (weights(i) == 0.0) {
212 centered.row(i).setZero();
213 }
214 }
215 return quadraticOlsDualPointFromDesign(
216 centered, y, false, observation_weights);
217 }
218
219 Eigen::MatrixXd theta(y.rows(), y.cols());
220 for (Eigen::Index k = 0; k < y.cols(); ++k) {
221 auto fit = fitOls(x_effective, Eigen::VectorXd(y.col(k)), true);
222 const Eigen::VectorXd& beta = fit.second;
223 theta.col(k) = x_effective * beta - y.col(k);
224 theta.col(k).array() -= effective_centers.dot(beta);
225 }
226
227 return theta;
228}
229
230} // namespace detail
231
249template<typename MatrixType>
250double
252 const Eigen::VectorXd& beta,
253 Eigen::MatrixXd theta,
254 const std::unique_ptr<Loss>& loss,
255 const SortedL1Norm& sl1_norm,
256 const Eigen::ArrayXd& lambda,
257 const MatrixType& x,
258 const Eigen::MatrixXd& y,
259 const Eigen::VectorXd& x_centers,
260 const Eigen::VectorXd& x_scales,
261 const JitNormalization& jit_normalization,
262 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
263{
264 const int n = x.rows();
265 const Eigen::VectorXd weights =
266 detail::normalizeObservationWeights(observation_weights, n);
267 Eigen::VectorXd gradient(beta.size());
268
270 gradient, x, theta, x_centers, x_scales, weights, jit_normalization);
271
272 const double dual_norm = sl1_norm.dualNorm(gradient, lambda);
273 theta.array() /= std::max(1.0, dual_norm);
274
275 return loss->dual(theta, y, weights);
276}
277
300template<typename MatrixType>
301double
302computeDual(const Eigen::VectorXd& beta,
303 const Eigen::MatrixXd& eta,
304 const std::unique_ptr<Loss>& loss,
305 const SortedL1Norm& sl1_norm,
306 const Eigen::ArrayXd& lambda,
307 const MatrixType& x,
308 const Eigen::MatrixXd& y,
309 const Eigen::VectorXd& x_centers,
310 const Eigen::VectorXd& x_scales,
311 const JitNormalization& jit_normalization,
312 const bool intercept,
313 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
314{
315 Eigen::MatrixXd theta =
316 loss->dualPoint(eta, y, intercept, observation_weights);
317 return computeDualFromPoint(beta,
318 theta,
319 loss,
320 sl1_norm,
321 lambda,
322 x,
323 y,
324 x_centers,
325 x_scales,
326 jit_normalization,
327 observation_weights);
328}
329
330} // namespace slope
Class representing the Sorted L1 Norm.
double dualNorm(const Eigen::VectorXd &a, const Eigen::ArrayXd &lambda) const
Computes the dual norm of a vector.
Enums to control predictor standardization behavior.
The declartion of the Objctive class and its subclasses, which represent the data-fitting part of the...
Mathematical support functions for the slope package.
Namespace containing SLOPE regression implementation.
Definition clusters.h:11
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, const Eigen::VectorXd &observation_weights=Eigen::VectorXd())
Scales a candidate into the SLOPE dual constraint and evaluates it.
JitNormalization
Enums to control predictor standardization behavior.
double computeDual(const Eigen::VectorXd &beta, const Eigen::MatrixXd &eta, 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, const bool intercept, const Eigen::VectorXd &observation_weights=Eigen::VectorXd())
Computes the dual objective function value for SLOPE optimization.
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
Ordinary Least Squares (OLS) regression functionality.
The declaration of the SortedL1Norm class.