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 3 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 58 additions & 34 deletions src/stan/services/pathfinder/multi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ 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] return_lp Whether single pathfinder should return lp calculations.
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
* 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 returned. If `false`, no psis resampling is performed
* and (`num_paths` * `num_draws`) samples are returned.
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
* @return error_codes::OK if successful
*/
template <class Model, typename InitContext, typename InitWriter,
Expand All @@ -92,7 +100,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 return_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 +126,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], return_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 +167,68 @@ 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 && return_lp) {
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)";
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 && return_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
44 changes: 28 additions & 16 deletions src/stan/services/pathfinder/single.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,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 return_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 +241,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 (return_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 +582,11 @@ 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] return_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 +602,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 return_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 +866,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, return_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
Loading