Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ordinal regression GLM (ordered_logistic_glm_lpmf) #1252

Merged
merged 22 commits into from Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a0008b5
First wip implementation of ordered_logistic_glm_lpmf and a problemat…
t4c1 May 21, 2019
74ba255
overflow prevention
t4c1 May 21, 2019
388fd9b
added big checks
t4c1 May 21, 2019
c056c7d
performance improvements
t4c1 May 23, 2019
0bfeae1
completed tests and removed debugging code
t4c1 May 23, 2019
ed3b35f
added doxygen
t4c1 May 23, 2019
65a76dd
added newlines to end of new files
t4c1 May 23, 2019
991dba1
Merge commit 'f49f224561ea2f2e10082d721075d27161ea6f25' into HEAD
yashikno May 23, 2019
9705d67
[Jenkins] auto-formatting by clang-format version 6.0.0 (tags/google/…
stan-buildbot May 23, 2019
2122dd5
fixed cpplint
t4c1 May 23, 2019
4837bca
fixed headers
t4c1 May 23, 2019
30fdb65
changed input types and enabled broadcasting
t4c1 May 30, 2019
a279322
optimized log1p_exp calculations
t4c1 May 30, 2019
c3cd074
[Jenkins] auto-formatting by clang-format version 6.0.0 (tags/google/…
stan-buildbot May 30, 2019
5440978
Merge branch 'develop' into CPU_ordered_logistic_glm_lpmf
t4c1 Jun 10, 2019
3efdbee
underflow prevention
t4c1 Jun 10, 2019
1dad46b
[Jenkins] auto-formatting by clang-format version 6.0.0 (tags/google/…
stan-buildbot Jun 10, 2019
84e628a
Merge branch 'develop' into CPU_ordered_logistic_glm_lpmf
t4c1 Jun 24, 2019
94f4635
updated to use single meta include
t4c1 Jun 24, 2019
51a4617
Merge branch 'develop' into CPU_ordered_logistic_glm_lpmf
t4c1 Aug 30, 2019
bd54230
replace is_constant_struct with is_constant_all
t4c1 Aug 30, 2019
7973dbc
addressed review comments
t4c1 Sep 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions stan/math/prim/mat.hpp
Expand Up @@ -316,6 +316,7 @@
#include <stan/math/prim/mat/prob/normal_id_glm_log.hpp>
#include <stan/math/prim/mat/prob/normal_id_glm_lpdf.hpp>
#include <stan/math/prim/mat/prob/ordered_logistic_log.hpp>
#include <stan/math/prim/mat/prob/ordered_logistic_glm_lpmf.hpp>
#include <stan/math/prim/mat/prob/ordered_logistic_lpmf.hpp>
#include <stan/math/prim/mat/prob/ordered_logistic_rng.hpp>
#include <stan/math/prim/mat/prob/ordered_probit_log.hpp>
Expand Down
213 changes: 213 additions & 0 deletions stan/math/prim/mat/prob/ordered_logistic_glm_lpmf.hpp
@@ -0,0 +1,213 @@
#ifndef STAN_MATH_PRIM_MAT_PROB_ORDERED_LOGISTIC_GLM_LPMF_HPP
#define STAN_MATH_PRIM_MAT_PROB_ORDERED_LOGISTIC_GLM_LPMF_HPP

#include <stan/math/prim/scal/err/check_consistent_sizes.hpp>
#include <stan/math/prim/scal/err/check_bounded.hpp>
#include <stan/math/prim/scal/err/check_finite.hpp>
#include <stan/math/prim/mat/fun/value_of_rec.hpp>
#include <stan/math/prim/arr/fun/value_of_rec.hpp>
#include <stan/math/prim/scal/fun/size_zero.hpp>
#include <stan/math/prim/mat/err/check_ordered.hpp>
#include <stan/math/prim/arr/err/check_ordered.hpp>
#include <stan/math/prim/mat/fun/log1m_exp.hpp>
#include <stan/math/prim/meta.hpp>
#include <cmath>

namespace stan {
namespace math {

/**
* Returns the log PMF of the ordinal regression Generalized Linear Model (GLM).
* This is equivalent to and faster than ordered_logistic_lpmf(y, x * beta,
* cuts).
*
* @tparam T_y type of integer vector of classes. It can be either
* `std::vector<int>` or `int`.
* @tparam T_x_scalar type of elements in the matrix of independent variables
* (features)
* @tparam T_x_rows compile-time number of rows of `x`. It can be either
* `Eigen::Dynamic` or 1.
* @tparam T_beta_scalar type of a scalar in the vector of weights
* @tparam T_cuts_scalar type of a scalar in the vector of cutpoints
* @param y a scalar or vector of classes. If it is a scalar it will be
* broadcast - used for all instances. Values should be between 1 and number of
* classes, including endpoints.
* @param x design matrix or row vector. If it is a row vector it will be
* broadcast - used for all instances.
* @param beta weight vector
* @param cuts cutpoints vector
* @return log probability
* @throw std::domain_error If any class is not between 1 and
* the number of cutpoints plus 2 or if the cutpoint vector is not sorted in
* ascending order or any input is not finite
* @throw std::invalid_argument if container sizes mismatch.
*/
template <bool propto, typename T_y, typename T_x_scalar, int T_x_rows,
typename T_beta_scalar, typename T_cuts_scalar>
typename stan::return_type_t<T_x_scalar, T_beta_scalar, T_cuts_scalar>
ordered_logistic_glm_lpmf(
const T_y& y, const Eigen::Matrix<T_x_scalar, T_x_rows, Eigen::Dynamic>& x,
const Eigen::Matrix<T_beta_scalar, Eigen::Dynamic, 1>& beta,
const Eigen::Matrix<T_cuts_scalar, Eigen::Dynamic, 1>& cuts) {
using Eigen::Array;
using Eigen::Dynamic;
using Eigen::Matrix;
using Eigen::VectorXd;
using std::exp;
using std::isfinite;

typedef typename partials_return_type<T_y, T_x_scalar, T_beta_scalar,
T_cuts_scalar>::type T_partials_return;
typedef typename std::conditional_t<T_x_rows == 1, double,
Array<double, Dynamic, 1>>
T_location;

static const char* function = "ordered_logistic_glm_lpmf";

const size_t N_instances = T_x_rows == 1 ? length(y) : x.rows();
const size_t N_attributes = x.cols();
const size_t N_classes = length(cuts) + 1;

if (is_vector<T_y>::value && T_x_rows != 1) {
check_consistent_size(function, "Vector of dependent variables", y,
N_instances);
}
check_consistent_size(function, "Weight vector", beta, N_attributes);
check_bounded(function, "Vector of dependent variables", y, 1, N_classes);
check_ordered(function, "Cut-points", cuts);
check_finite(function, "Final cut-point", cuts[N_classes - 2]);
check_finite(function, "First cut-point", cuts[0]);
andrjohns marked this conversation as resolved.
Show resolved Hide resolved

if (size_zero(y, cuts))
return 0;

if (!include_summand<propto, T_x_scalar, T_beta_scalar, T_cuts_scalar>::value)
return 0;

const auto& x_val = value_of_rec(x);
const auto& beta_val = value_of_rec(beta);
const auto& cuts_val = value_of_rec(cuts);

const auto& beta_val_vec = as_column_vector_or_scalar(beta_val);
const auto& cuts_val_vec = as_column_vector_or_scalar(cuts_val);

scalar_seq_view<T_y> y_seq(y);
Array<double, Dynamic, 1> cuts_y1(N_instances), cuts_y2(N_instances);
for (int i = 0; i < N_instances; i++) {
int c = y_seq[i];
if (c != N_classes) {
cuts_y1[i] = cuts_val_vec[c - 1];
} else {
cuts_y1[i] = INFINITY;
}
if (c != 1) {
cuts_y2[i] = cuts_val_vec[c - 2];
} else {
cuts_y2[i] = -INFINITY;
}
}

T_location location = x_val * beta_val_vec;
if (!isfinite(sum(location))) {
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
check_finite(function, "Weight vector", beta);
check_finite(function, "Matrix of independent variables", x);
}

Array<double, Dynamic, 1> cut2 = location - cuts_y2;
Array<double, Dynamic, 1> cut1 = location - cuts_y1;

// Not immediately evaluating next two expressions benefits performance
auto m_log_1p_exp_cut1
= (cut1 > 0.0).select(-cut1, 0) - (-cut1.abs()).exp().log1p();
auto m_log_1p_exp_m_cut2
= (cut2 <= 0.0).select(cut2, 0) - (-cut2.abs()).exp().log1p();

T_partials_return logp(0);
if (is_vector<T_y>::value) {
Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, 1>> y_vec(&y_seq[0],
y_seq.size());
logp = y_vec.cwiseEqual(1)
.select(m_log_1p_exp_cut1,
y_vec.cwiseEqual(N_classes).select(
m_log_1p_exp_m_cut2,
m_log_1p_exp_m_cut2 + log1m_exp(cut1 - cut2).array()
+ m_log_1p_exp_cut1))
.sum();
} else {
if (y_seq[0] == 1) {
logp = m_log_1p_exp_cut1.sum();
} else if (y_seq[0] == N_classes) {
logp = m_log_1p_exp_m_cut2.sum();
} else {
logp = (m_log_1p_exp_m_cut2 + log1m_exp(cut1 - cut2).array()
+ m_log_1p_exp_cut1)
.sum();
}
}

operands_and_partials<Matrix<T_x_scalar, T_x_rows, Dynamic>,
Eigen::Matrix<T_beta_scalar, Eigen::Dynamic, 1>,
Eigen::Matrix<T_cuts_scalar, Eigen::Dynamic, 1>>
ops_partials(x, beta, cuts);
if (!is_constant_all<T_x_scalar, T_beta_scalar, T_cuts_scalar>::value) {
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
Array<double, Dynamic, 1> exp_m_cut1 = exp(-cut1);
Array<double, Dynamic, 1> exp_m_cut2 = exp(-cut2);
Array<double, Dynamic, 1> exp_cuts_diff = exp(cuts_y2 - cuts_y1);
Array<double, Dynamic, 1> d1
= (cut2 > 0).select(exp_m_cut2 / (1 + exp_m_cut2), 1 / (1 + exp(cut2)))
- exp_cuts_diff / (exp_cuts_diff - 1);
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
Array<double, Dynamic, 1> d2
= 1 / (1 - exp_cuts_diff)
- (cut1 > 0).select(exp_m_cut1 / (1 + exp_m_cut1),
1 / (1 + exp(cut1)));
if (!is_constant_all<T_x_scalar, T_beta_scalar>::value) {
Matrix<double, 1, Dynamic> location_derivative = d1 - d2;
if (!is_constant_all<T_x_scalar>::value) {
if (T_x_rows == 1) {
ops_partials.edge1_.partials_
= beta_val_vec * location_derivative.sum();
} else {
ops_partials.edge1_.partials_
= (beta_val_vec * location_derivative).transpose();
}
}
if (!is_constant_all<T_beta_scalar>::value) {
if (T_x_rows == 1) {
ops_partials.edge2_.partials_
= (location_derivative * x_val.replicate(N_instances, 1))
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
.transpose();
} else {
ops_partials.edge2_.partials_
= (location_derivative * x_val).transpose();
}
}
}
if (!is_constant_all<T_cuts_scalar>::value) {
for (int i = 0; i < N_instances; i++) {
int c = y_seq[i];
if (c != N_classes) {
ops_partials.edge3_.partials_[c - 1] += d2[i];
}
if (c != 1) {
ops_partials.edge3_.partials_[c - 2] -= d1[i];
}
}
}
}
return ops_partials.build(logp);
}

template <typename T_y, typename T_x_scalar, int T_x_rows,
typename T_beta_scalar, typename T_cuts_scalar>
typename stan::return_type_t<T_x_scalar, T_beta_scalar, T_cuts_scalar>
ordered_logistic_glm_lpmf(
const T_y& y, const Eigen::Matrix<T_x_scalar, T_x_rows, Eigen::Dynamic>& x,
const Eigen::Matrix<T_beta_scalar, Eigen::Dynamic, 1>& beta,
const Eigen::Matrix<T_cuts_scalar, Eigen::Dynamic, 1>& cuts) {
return ordered_logistic_glm_lpmf<false>(y, x, beta, cuts);
}

} // namespace math
} // namespace stan

#endif