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

Adds option to turn off psis resampling and lp calculation for pathfinder #3249

Merged
merged 22 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
53790e3
Adds option to turn off psis resampling and lp calculation for pathfi…
SteveBronder Jan 2, 2024
ef527c9
cpplint
SteveBronder Jan 2, 2024
f2369ae
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 2, 2024
b7c9fe7
Merge remote-tracking branch 'origin/develop' into feature/no-psis-or…
SteveBronder Jan 3, 2024
b7d44e8
update with review
SteveBronder Jan 3, 2024
26b1656
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 3, 2024
93c3958
fix typo in vals vs values for unique_stream_writer
SteveBronder Jan 4, 2024
d69a2df
add virtual overloads for vector and row vector printing for unique_s…
SteveBronder Jan 4, 2024
1e776fa
Remove array from writer overloads and have pathfinder only write wit…
SteveBronder Jan 5, 2024
5c8aa63
Merge commit '7e9342f13e9b71e5aac2040a90c59aa56fd12842' into HEAD
yashikno Jan 5, 2024
6e7c832
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 5, 2024
ed0449a
add signatures for blocks
SteveBronder Jan 5, 2024
e14b330
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 5, 2024
eb1d865
names for writer block arguments
SteveBronder Jan 5, 2024
1284bb1
use Ref instead of multiple overloads for writer
SteveBronder Jan 5, 2024
de22ba4
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 5, 2024
62d154c
Remove logic for row vector
SteveBronder Jan 5, 2024
eb11a48
Remove logic for row vector
SteveBronder Jan 5, 2024
6892ccb
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 5, 2024
6b4932c
Use {} instead of () for CommaFormat defaults in unique_stream_writer
SteveBronder Jan 5, 2024
08251a2
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Jan 5, 2024
1ac35a7
remove old test code
SteveBronder Jan 5, 2024
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
18 changes: 18 additions & 0 deletions src/stan/callbacks/unique_stream_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ class unique_stream_writer final : public writer {
*output_ << values.transpose().format(CommaInitFmt);
}

/**
* Writes multiple rows and columns of values in csv format.
*
* Note: the precision of the output is determined by the settings
* of the stream on construction.
*
* @param[in] values An array of values. The input is expected to have
* parameters in the rows and samples in the columns. The array is then
* transposed for the output.
*/
void operator()(const Eigen::Array<double, -1, -1>& values) {
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
if (output_ == nullptr)
return;
Eigen::IOFormat CommaInitFmt(Eigen::StreamPrecision, Eigen::DontAlignCols,
", ", "", "", "\n", "", "");
*output_ << values.transpose().format(CommaInitFmt);
}

/**
* Writes the comment_prefix to the stream followed by a newline.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/stan/callbacks/writer.hpp
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ class writer {
* transposed for the output.
*/
virtual void operator()(const Eigen::MatrixXd& values) {}

/**
* Writes multiple rows and columns of values in csv format.
*
* Note: the precision of the output is determined by the settings
* of the stream on construction.
*
* @param[in] values An array of values. The input is expected to have
* parameters in the rows and samples in the columns. The array is then
* transposed for the output.
*/
void operator()(const Eigen::Array<double, -1, -1>& vals) {}
};

} // namespace callbacks
Expand Down
101 changes: 64 additions & 37 deletions src/stan/services/pathfinder/multi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ namespace pathfinder {
* @param[in,out] parameter_writer output for parameter values
* @param[in,out] diagnostic_writer output for diagnostics values,
* `error_codes::SOFTWARE` for failures
* @param[in] calculate_lp Whether single pathfinder should return lp
* calculations. If `true`, calculates the joint log probability for each
* sample. If `false`, (`num_draws` - `num_elbo_draws`) of the joint log
* probability calculations will be `NA` and psis resampling will not be
* performed.
* @param[in] psis_resampling If `true`, psis resampling is performed over the
* samples returned by all of the individual pathfinders and `num_multi_draws`
* samples are written to `parameter_writer`. If `false`, no psis resampling is
* performed and (`num_paths` * `num_draws`) samples are written to
* `parameter_writer`.
* @return error_codes::OK if successful
*/
template <class Model, typename InitContext, typename InitWriter,
Expand All @@ -92,7 +102,8 @@ inline int pathfinder_lbfgs_multi(
callbacks::logger& logger, InitWriter&& init_writers,
std::vector<SingleParamWriter>& single_path_parameter_writer,
std::vector<SingleDiagnosticWriter>& single_path_diagnostic_writer,
ParamWriter& parameter_writer, DiagnosticWriter& diagnostic_writer) {
ParamWriter& parameter_writer, DiagnosticWriter& diagnostic_writer,
bool calculate_lp = true, bool psis_resample = true) {
const auto start_pathfinders_time = std::chrono::steady_clock::now();
std::vector<std::string> param_names;
param_names.push_back("lp_approx__");
Expand All @@ -117,7 +128,7 @@ inline int pathfinder_lbfgs_multi(
num_elbo_draws, num_draws, save_iterations, refresh,
interrupt, logger, init_writers[iter],
single_path_parameter_writer[iter],
single_path_diagnostic_writer[iter]);
single_path_diagnostic_writer[iter], calculate_lp);
if (unlikely(std::get<0>(pathfinder_ret) != error_codes::OK)) {
logger.error(std::string("Pathfinder iteration: ")
+ std::to_string(iter) + " failed.");
Expand Down Expand Up @@ -158,53 +169,69 @@ inline int pathfinder_lbfgs_multi(
for (auto&& ilpr : individual_lp_ratios) {
num_returned_samples += ilpr.size();
}
Eigen::Array<double, Eigen::Dynamic, 1> lp_ratios(num_returned_samples);
// Rows are individual parameters and columns are samples per iteration
Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> samples(
individual_samples[0].rows(), num_returned_samples);
Eigen::Index filling_start_row = 0;
for (size_t i = 0; i < successful_pathfinders; ++i) {
const Eigen::Index individ_num_samples = individual_lp_ratios[i].size();
lp_ratios.segment(filling_start_row, individ_num_samples)
= individual_lp_ratios[i];
const Eigen::Index individ_num_samples = individual_samples[i].cols();
samples.middleCols(filling_start_row, individ_num_samples)
= individual_samples[i];
filling_start_row += individ_num_samples;
}
const auto tail_len = std::min(0.2 * num_returned_samples,
3 * std::sqrt(num_returned_samples));
Eigen::Array<double, Eigen::Dynamic, 1> weight_vals
= stan::services::psis::psis_weights(lp_ratios, tail_len, logger);
boost::ecuyer1988 rng
= util::create_rng<boost::ecuyer1988>(random_seed, stride_id);
boost::variate_generator<
boost::ecuyer1988&,
boost::random::discrete_distribution<Eigen::Index, double>>
rand_psis_idx(rng,
boost::random::discrete_distribution<Eigen::Index, double>(
boost::iterator_range<double*>(
weight_vals.data(),
weight_vals.data() + weight_vals.size())));
for (size_t i = 0; i <= num_multi_draws - 1; ++i) {
parameter_writer(samples.col(rand_psis_idx()));
double psis_delta_time = 0;
if (psis_resample && calculate_lp) {
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
Eigen::Array<double, Eigen::Dynamic, 1> lp_ratios(num_returned_samples);
filling_start_row = 0;
for (size_t i = 0; i < successful_pathfinders; ++i) {
const Eigen::Index individ_num_samples = individual_lp_ratios[i].size();
lp_ratios.segment(filling_start_row, individ_num_samples)
= individual_lp_ratios[i];
filling_start_row += individ_num_samples;
}

const auto tail_len = std::min(0.2 * num_returned_samples,
3 * std::sqrt(num_returned_samples));
Eigen::Array<double, Eigen::Dynamic, 1> weight_vals
= stan::services::psis::psis_weights(lp_ratios, tail_len, logger);
boost::ecuyer1988 rng
= util::create_rng<boost::ecuyer1988>(random_seed, stride_id);
boost::variate_generator<
boost::ecuyer1988&,
boost::random::discrete_distribution<Eigen::Index, double>>
rand_psis_idx(
rng, boost::random::discrete_distribution<Eigen::Index, double>(
boost::iterator_range<double*>(
weight_vals.data(),
weight_vals.data() + weight_vals.size())));
for (size_t i = 0; i <= num_multi_draws - 1; ++i) {
parameter_writer(samples.col(rand_psis_idx()));
}
const auto end_psis_time = std::chrono::steady_clock::now();
psis_delta_time
= stan::services::util::duration_diff(start_psis_time, end_psis_time);

} else {
parameter_writer(samples);
}
const auto end_psis_time = std::chrono::steady_clock::now();
double psis_delta_time
= stan::services::util::duration_diff(start_psis_time, end_psis_time);
parameter_writer();
const auto time_header = std::string("Elapsed Time: ");
std::string optim_time_str = time_header
+ std::to_string(pathfinders_delta_time)
+ " seconds (Pathfinders)";
std::string optim_time_str
= time_header + std::to_string(pathfinders_delta_time)
+ std::string(" seconds")
+ ((psis_resample && calculate_lp) ? " (Pathfinders)" : " (Total)");
parameter_writer(optim_time_str);
std::string psis_time_str = std::string(time_header.size(), ' ')
+ std::to_string(psis_delta_time)
+ " seconds (PSIS)";
parameter_writer(psis_time_str);
std::string total_time_str
= std::string(time_header.size(), ' ')
+ std::to_string(pathfinders_delta_time + psis_delta_time)
+ " seconds (Total)";
parameter_writer(total_time_str);
if (psis_resample && calculate_lp) {
std::string psis_time_str = std::string(time_header.size(), ' ')
+ std::to_string(psis_delta_time)
+ " seconds (PSIS)";
parameter_writer(psis_time_str);
std::string total_time_str
= std::string(time_header.size(), ' ')
+ std::to_string(pathfinders_delta_time + psis_delta_time)
+ " seconds (Total)";
parameter_writer(total_time_str);
}
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
parameter_writer();
return 0;
}
Expand Down
49 changes: 32 additions & 17 deletions src/stan/services/pathfinder/single.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ generate_matrix(Generator&& variate_generator, const Eigen::Index num_params,
* @param alpha The approximation of the diagonal hessian
* @param iter_msg The beginning of messages that includes the iteration number
* @param logger A callback writer for messages
* @param calculate_lp If true, calculate the log probability of the samples.
* Else set to `NaN` for each sample.
* @return A struct with the ELBO estimate along with the samples and log
* probability ratios.
*/
Expand All @@ -217,8 +219,8 @@ inline elbo_est_t est_approx_draws(LPF&& lp_fun, ConstrainF&& constrain_fun,
RNG&& rng,
const taylor_approx_t& taylor_approx,
size_t num_samples, const EigVec& alpha,
const std::string& iter_msg,
Logger&& logger) {
const std::string& iter_msg, Logger&& logger,
bool calculate_lp = true) {
boost::variate_generator<boost::ecuyer1988&, boost::normal_distribution<>>
rand_unit_gaus(rng, boost::normal_distribution<>());
const auto num_params = taylor_approx.x_center.size();
Expand All @@ -241,18 +243,25 @@ inline elbo_est_t est_approx_draws(LPF&& lp_fun, ConstrainF&& constrain_fun,
};
Eigen::VectorXd approx_samples_col;
std::stringstream pathfinder_ss;
for (Eigen::Index i = 0; i < num_samples; ++i) {
try {
approx_samples_col = approx_samples.col(i);
++lp_fun_calls;
lp_mat.coeffRef(i, 1) = lp_fun(approx_samples_col, pathfinder_ss);
} catch (const std::exception& e) {
lp_mat.coeffRef(i, 1) = -std::numeric_limits<double>::infinity();
Eigen::Array<double, Eigen::Dynamic, 1> lp_ratio;
if (calculate_lp) {
for (Eigen::Index i = 0; i < num_samples; ++i) {
try {
approx_samples_col = approx_samples.col(i);
++lp_fun_calls;
lp_mat.coeffRef(i, 1) = lp_fun(approx_samples_col, pathfinder_ss);
} catch (const std::exception& e) {
lp_mat.coeffRef(i, 1) = -std::numeric_limits<double>::infinity();
}
log_stream(logger, pathfinder_ss, iter_msg);
}
log_stream(logger, pathfinder_ss, iter_msg);
lp_ratio = lp_mat.col(1) - lp_mat.col(0);
} else {
lp_ratio = Eigen::Array<double, Eigen::Dynamic, 1>::Constant(
lp_mat.rows(), std::numeric_limits<double>::quiet_NaN());
lp_mat.col(1) = Eigen::Matrix<double, Eigen::Dynamic, 1>::Constant(
lp_mat.rows(), std::numeric_limits<double>::quiet_NaN());
}
Eigen::Array<double, Eigen::Dynamic, 1> lp_ratio
= lp_mat.col(1) - lp_mat.col(0);
if (ReturnElbo) {
double elbo = lp_ratio.mean();
return elbo_est_t{elbo, lp_fun_calls, std::move(approx_samples),
Expand Down Expand Up @@ -575,6 +584,12 @@ auto pathfinder_impl(RNG&& rng, LPFun&& lp_fun, ConstrainFun&& constrain_fun,
* @param[in,out] init_writer Writer callback for unconstrained inits
* @param[in,out] parameter_writer Writer callback for parameter values
* @param[in,out] diagnostic_writer output for diagnostics values
* @param[in] calculate_lp Whether single pathfinder should return lp
* calculations. If `true`, calculates the joint log probability for each
* sample. If `false`, (`num_draws` - `num_elbo_draws`) of the joint log
* probability calculations will be `NA` and psis resampling will not be
* performed. Setting this parameter to `false` will also set all of the lp
* ratios to `NaN`.
* @return If `ReturnLpSamples` is `true`, returns a tuple of the error code,
* approximate draws, and a vector of the lp ratio. If `false`, only returns an
* error code `error_codes::OK` if successful, `error_codes::SOFTWARE`
Expand All @@ -590,7 +605,7 @@ inline auto pathfinder_lbfgs_single(
int num_elbo_draws, int num_draws, bool save_iterations, int refresh,
callbacks::interrupt& interrupt, callbacks::logger& logger,
callbacks::writer& init_writer, ParamWriter& parameter_writer,
DiagnosticWriter& diagnostic_writer) {
DiagnosticWriter& diagnostic_writer, bool calculate_lp = true) {
const auto start_pathfinder_time = std::chrono::steady_clock::now();
boost::ecuyer1988 rng
= util::create_rng<boost::ecuyer1988>(random_seed, stride_id);
Expand Down Expand Up @@ -854,13 +869,13 @@ inline auto pathfinder_lbfgs_single(
try {
internal::elbo_est_t est_draws = internal::est_approx_draws<false>(
lp_fun, constrain_fun, rng, taylor_approx_best, remaining_draws,
taylor_approx_best.alpha, path_num, logger);
taylor_approx_best.alpha, path_num, logger, calculate_lp);
num_evals += est_draws.fn_calls;
auto&& new_lp_ratio = est_draws.lp_ratio;
auto&& lp_draws = est_draws.lp_mat;
auto&& new_draws = est_draws.repeat_draws;
lp_ratio = Eigen::Array<double, Eigen::Dynamic, 1>(
new_lp_ratio.size() + elbo_lp_ratio.size());
lp_ratio = Eigen::Array<double, Eigen::Dynamic, 1>(elbo_lp_ratio.size()
+ new_lp_ratio.size());
lp_ratio.head(elbo_lp_ratio.size()) = elbo_lp_ratio.array();
lp_ratio.tail(new_lp_ratio.size()) = new_lp_ratio.array();
const auto total_size = elbo_draws.cols() + new_draws.cols();
Expand Down Expand Up @@ -921,7 +936,7 @@ inline auto pathfinder_lbfgs_single(
}
lp_ratio = std::move(elbo_best.lp_ratio);
}
parameter_writer(constrained_draws_mat.matrix());
parameter_writer(constrained_draws_mat);
parameter_writer();
const auto end_pathfinder_time = std::chrono::steady_clock::now();
const double pathfinder_delta_time = stan::services::util::duration_diff(
Expand Down