bnmf-algs
alloc_model_funcs.hpp
Go to the documentation of this file.
1 #pragma once
2 
4 #include "defs.hpp"
5 #include "util/generator.hpp"
6 #include "util/util.hpp"
7 #include <gsl/gsl_randist.h>
8 #include <gsl/gsl_sf_gamma.h>
9 #include <tuple>
10 #include <vector>
11 
12 namespace bnmf_algs {
13 namespace alloc_model {
14 // forward declaration needed to use this in details functions
15 template <typename T, typename Scalar>
16 double log_marginal_S(const tensor_t<T, 3>& S,
17  const Params<Scalar>& model_params);
18 } // namespace alloc_model
19 
20 namespace details {
21 
33 template <typename T, typename Scalar>
35  const std::vector<Scalar>& alpha) {
36  const long x = S.dimension(0);
37  const long y = S.dimension(1);
38  const long z = S.dimension(2);
39 
40  // loggamma(sum(alpha))
41  const double log_gamma_sum =
42  gsl_sf_lngamma(std::accumulate(alpha.begin(), alpha.end(), 0.0));
43 
44  // sum(loggamma(alpha))
45  std::vector<double> log_gamma_alpha(alpha.size());
47  alpha.begin(), alpha.end(), log_gamma_alpha.begin(),
48  [](const Scalar alpha_i) { return gsl_sf_lngamma(alpha_i); });
49 
50  const double sum_log_gamma =
51  std::accumulate(log_gamma_alpha.begin(), log_gamma_alpha.end(), 0.0);
52 
53  double first = 0;
54  #pragma omp parallel for reduction(+:first)
55  for (int k = 0; k < z; ++k) {
56  double sum = 0;
57  for (int i = 0; i < x; ++i) {
58  sum += alpha[i];
59  for (int j = 0; j < y; ++j) {
60  sum += S(i, j, k);
61  }
62  }
63  first -= gsl_sf_lngamma(sum);
64 
65  for (int i = 0; i < x; ++i) {
66  sum = alpha[i];
67  for (int j = 0; j < y; ++j) {
68  sum += S(i, j, k);
69  }
70  first += gsl_sf_lngamma(sum);
71  }
72  }
73 
74  double base = log_gamma_sum * z - sum_log_gamma * z;
75 
76  return base + first;
77 }
78 
90 template <typename T, typename Scalar>
92  const std::vector<Scalar>& beta) {
93  const long x = S.dimension(0);
94  const long y = S.dimension(1);
95  const long z = S.dimension(2);
96 
97  // loggamma(sum(beta))
98  const double log_gamma_sum =
99  gsl_sf_lngamma(std::accumulate(beta.begin(), beta.end(), 0.0));
100 
101  // sum(loggamma(beta))
102  std::vector<double> log_gamma_beta(beta.size());
103  std::transform(beta.begin(), beta.end(), log_gamma_beta.begin(),
104  [](const Scalar beta) { return gsl_sf_lngamma(beta); });
105 
106  const double sum_log_gamma =
107  std::accumulate(log_gamma_beta.begin(), log_gamma_beta.end(), 0.0);
108 
109  double second = 0;
110  #pragma omp parallel for reduction(+:second)
111  for (int j = 0; j < y; ++j) {
112  double sum = 0;
113  for (int k = 0; k < z; ++k) {
114  sum += beta[k];
115  for (int i = 0; i < x; ++i) {
116  sum += S(i, j, k);
117  }
118  }
119  second -= gsl_sf_lngamma(sum);
120 
121  for (int k = 0; k < z; ++k) {
122  sum = beta[k];
123  for (int i = 0; i < x; ++i) {
124  sum += S(i, j, k);
125  }
126  second += gsl_sf_lngamma(sum);
127  }
128  }
129 
130  double base = log_gamma_sum * y - sum_log_gamma * y;
131  return base + second;
132 }
133 
145 template <typename T, typename Scalar>
146 double compute_third_term(const tensor_t<T, 3>& S, Scalar a, Scalar b) {
147  const long x = S.dimension(0);
148  const long y = S.dimension(1);
149  const long z = S.dimension(2);
150 
151  const double log_gamma = -gsl_sf_lngamma(a);
152  const double a_log_b = a * std::log(b);
153 
154  double third = 0;
155  #pragma omp parallel for reduction(+:third)
156  for (int j = 0; j < y; ++j) {
157  double sum = a;
158  for (int i = 0; i < x; ++i) {
159  for (int k = 0; k < z; ++k) {
160  sum += S(i, j, k);
161  }
162  }
163  third += gsl_sf_lngamma(sum);
164  third -= sum * std::log(b + 1);
165  }
166 
167  double base = log_gamma * y + a_log_b * y;
168  return base + third;
169 }
170 
179 template <typename T> double compute_fourth_term(const tensor_t<T, 3>& S) {
180  const long x = S.dimension(0);
181  const long y = S.dimension(1);
182  const long z = S.dimension(2);
183 
184  double fourth = 0;
185  #pragma omp parallel for reduction(+:fourth)
186  for (int i = 0; i < x; ++i) {
187  for (int j = 0; j < y; ++j) {
188  for (int k = 0; k < z; ++k) {
189  fourth += gsl_sf_lngamma(S(i, j, k) + 1);
190  }
191  }
192  }
193  return fourth;
194 }
195 
204 template <typename Integer, typename Scalar> class TotalMarginalCalculator {
205  public:
214  const alloc_model::Params<Scalar>& model_params)
215  : X(X), model_params(model_params),
216  S(X.rows(), X.cols(), model_params.beta.size()) {
217  // initialize variables to use during recursive computation
218 
219  // get all partitions for every nonzero element of X
220  const Integer z = model_params.beta.size();
221  for (long i = 0; i < X.rows(); ++i) {
222  for (long j = 0; j < X.cols(); ++j) {
223  if (X(i, j) != 0) {
224  ii.push_back(i);
225  jj.push_back(j);
226  alloc_vec.push_back(
227  util::partition_change_indices(X(i, j), z));
228  }
229  }
230  }
231 
232  // initialize allocation tensors
233  S.setZero();
234  for (long i = 0; i < X.rows(); ++i) {
235  for (long j = 0; j < X.cols(); ++j) {
236  S(i, j, 0) = X(i, j);
237  }
238  }
239  S_pjk = S.sum(shape<1>({0}));
240  S_ipk = S.sum(shape<1>({1}));
241  S_ppk = S.sum(shape<2>({0, 1}));
242 
243  sum_alpha = std::accumulate(model_params.alpha.begin(),
244  model_params.alpha.end(), Scalar());
245  }
246 
254  double calc_marginal() {
255  const double init_log_marginal =
256  alloc_model::log_marginal_S(S, model_params);
257 
258  return marginal_recursive(0, init_log_marginal);
259  }
260 
261  private:
272  double log_marginal_change_on_increment(size_t i, size_t j, size_t k) {
273  return std::log(model_params.alpha[i] + S_ipk(i, k)) -
274  std::log(sum_alpha + S_ppk(k)) - std::log(1 + S(i, j, k)) +
275  std::log(model_params.beta[k] + S_pjk(j, k));
276  }
277 
288  double log_marginal_change_on_decrement(size_t i, size_t j, size_t k) {
289  double result = -(std::log(model_params.alpha[i] + S_ipk(i, k) - 1) -
290  std::log(sum_alpha + S_ppk(k) - 1) -
291  std::log(1 + S(i, j, k) - 1) +
292  std::log(model_params.beta[k] + S_pjk(j, k) - 1));
293 
294  return result;
295  }
296 
318  double marginal_recursive(const size_t fiber_index,
319  double prev_log_marginal) {
320 
321  // get current fiber partitions and indices
322  const auto& part_changes = alloc_vec[fiber_index];
323  const size_t i = ii[fiber_index];
324  const size_t j = jj[fiber_index];
325 
326  const Integer old_value = S(i, j, 0);
327 
328  // if true, then base case; else, recursive part.
329  const bool last_fiber = fiber_index == (ii.size() - 1);
330 
331  double result =
332  last_fiber ? std::exp(prev_log_marginal)
333  : marginal_recursive(fiber_index + 1, prev_log_marginal);
334 
335  // calculate log marginal of every possible tensor and accumulate
336  // results
337  size_t incr_idx, decr_idx;
338  double increment_change, decrement_change, new_log_marginal;
339  for (const auto& change_idx : part_changes) {
340  // indices of elements to decrement and increment
341  std::tie(decr_idx, incr_idx) = change_idx;
342 
343  decrement_change = log_marginal_change_on_decrement(i, j, decr_idx);
344 
345  // modify elements
346  --S(i, j, decr_idx);
347  --S_pjk(j, decr_idx);
348  --S_ipk(i, decr_idx);
349  --S_ppk(decr_idx);
350 
351  increment_change = log_marginal_change_on_increment(i, j, incr_idx);
352 
353  // modify elements
354  ++S(i, j, incr_idx);
355  ++S_pjk(j, incr_idx);
356  ++S_ipk(i, incr_idx);
357  ++S_ppk(incr_idx);
358 
359  new_log_marginal =
360  prev_log_marginal + increment_change + decrement_change;
361 
362  // accumulate
363  if (last_fiber) {
364  result += std::exp(new_log_marginal);
365  } else {
366  result += marginal_recursive(fiber_index + 1, new_log_marginal);
367  }
368 
369  prev_log_marginal = new_log_marginal;
370  }
371 
372  // make S same as before
373  const auto last_index = S.dimension(2) - 1;
374  S(i, j, last_index) = 0;
375  S(i, j, 0) = old_value;
376  S_pjk(j, last_index) -= old_value;
377  S_pjk(j, 0) += old_value;
378  S_ipk(i, last_index) -= old_value;
379  S_ipk(i, 0) += old_value;
380  S_ppk(last_index) -= old_value;
381  S_ppk(0) += old_value;
382 
383  return result;
384  }
385 
386  private:
390  const matrix_t<Integer>& X;
394  const alloc_model::Params<Scalar>& model_params;
403  tensor_t<Integer, 2> S_pjk;
407  tensor_t<Integer, 2> S_ipk;
411  tensor_t<Integer, 1> S_ppk;
415  Scalar sum_alpha;
432 };
433 } // namespace details
434 
439 namespace alloc_model {
440 
461 template <typename T, typename Scalar>
463 bnmf_priors(const shape<3>& tensor_shape, const Params<Scalar>& model_params) {
464  size_t x = tensor_shape[0], y = tensor_shape[1], z = tensor_shape[2];
465 
466  BNMF_ASSERT(model_params.alpha.size() == x,
467  "Number of dirichlet parameters alpha must be equal to x");
468  BNMF_ASSERT(model_params.beta.size() == z,
469  "Number of dirichlet parameters beta must be equal to z");
470 
471  util::gsl_rng_wrapper rand_gen(gsl_rng_alloc(gsl_rng_taus), gsl_rng_free);
472 
473  // generate prior_L
474  vector_t<T> prior_L(y);
475  for (size_t i = 0; i < y; ++i) {
476  prior_L(i) =
477  gsl_ran_gamma(rand_gen.get(), model_params.a, model_params.b);
478  }
479 
480  // generate prior_W
481  matrix_t<T> prior_W(x, z);
482  vector_t<T> dirichlet_variates(x);
483  for (size_t i = 0; i < z; ++i) {
484  gsl_ran_dirichlet(rand_gen.get(), x, model_params.alpha.data(),
485  dirichlet_variates.data());
486 
487  for (size_t j = 0; j < x; ++j) {
488  prior_W(j, i) = dirichlet_variates(j);
489  }
490  }
491 
492  // generate prior_H
493  matrix_t<T> prior_H(z, y);
494  dirichlet_variates = vector_t<T>(z);
495  for (size_t i = 0; i < y; ++i) {
496  gsl_ran_dirichlet(rand_gen.get(), z, model_params.beta.data(),
497  dirichlet_variates.data());
498 
499  for (size_t j = 0; j < z; ++j) {
500  prior_H(j, i) = dirichlet_variates(j);
501  }
502  }
503 
504  return std::make_tuple(prior_W, prior_H, prior_L);
505 }
506 
527 template <typename T>
528 tensor_t<T, 3> sample_S(const matrix_t<T>& prior_W, const matrix_t<T>& prior_H,
529  const vector_t<T>& prior_L) {
530  auto x = static_cast<size_t>(prior_W.rows());
531  auto y = static_cast<size_t>(prior_L.cols());
532  auto z = static_cast<size_t>(prior_H.rows());
533 
534  BNMF_ASSERT(prior_W.cols() == prior_H.rows(),
535  "Number of columns of W is different than number of rows of H");
536  BNMF_ASSERT(prior_H.cols() == prior_L.cols(),
537  "Number of columns of H is different than size of L");
538 
539  // TODO: Is it OK to construct this rng object every time?
540  util::gsl_rng_wrapper rand_gen(gsl_rng_alloc(gsl_rng_taus), gsl_rng_free);
541  tensor_t<T, 3> sample(x, y, z);
542  double mu;
543  for (size_t i = 0; i < x; ++i) {
544  for (size_t j = 0; j < y; ++j) {
545  for (size_t k = 0; k < z; ++k) {
546  mu = prior_W(i, k) * prior_H(k, j) * prior_L(j);
547  sample(i, j, k) = gsl_ran_poisson(rand_gen.get(), mu);
548  }
549  }
550  }
551  return sample;
552 }
553 
573 template <typename T, typename Scalar>
575  const Params<Scalar>& model_params) {
576  BNMF_ASSERT(model_params.alpha.size() ==
577  static_cast<size_t>(S.dimension(0)),
578  "Number of alpha parameters must be equal to S.dimension(0)");
579  BNMF_ASSERT(model_params.beta.size() == static_cast<size_t>(S.dimension(2)),
580  "Number of alpha parameters must be equal to S.dimension(2)");
581 
582  return details::compute_first_term(S, model_params.alpha) +
583  details::compute_second_term(S, model_params.beta) +
584  details::compute_third_term(S, model_params.a, model_params.b) -
586 }
587 
599 template <typename Integer, typename Scalar>
601  const Params<Scalar>& model_params) {
602  BNMF_ASSERT((X.array() >= 0).all(),
603  "X must be nonnegative in alloc_model::total_log_marginal");
604  BNMF_ASSERT(static_cast<size_t>(X.rows()) == model_params.alpha.size(),
605  "Model parameters are incompatible with given matrix X in "
606  "alloc_model::total_log_marginal");
607 
609  double marginal = calc.calc_marginal();
610 
611  return std::log(marginal);
612 }
613 
614 } // namespace alloc_model
615 } // namespace bnmf_algs
Structure to hold the parameters for the Allocation Model .
Definition: alloc_model_params.hpp:25
T tie(T...args)
TotalMarginalCalculator(const matrix_t< Integer > &X, const alloc_model::Params< Scalar > &model_params)
Construct the calculator class and initialize computation variables.
Definition: alloc_model_funcs.hpp:213
T exp(T...args)
Eigen::Tensor< Scalar, N, Eigen::RowMajor > tensor_t
Tensor type used in the computations.
Definition: defs.hpp:52
T log(T...args)
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > matrix_t
Matrix type used in the computations.
Definition: defs.hpp:41
T make_tuple(T...args)
T end(T...args)
double total_log_marginal(const matrix_t< Integer > &X, const Params< Scalar > &model_params)
Calculate total marginal value, where is model parameters.
Definition: alloc_model_funcs.hpp:600
Eigen::array< size_t, N > shape
Shape of vectors, matrices, tensors, etc.
Definition: defs.hpp:66
double compute_first_term(const tensor_t< T, 3 > &S, const std::vector< Scalar > &alpha)
Compute the first term of the sum when calculating log marginal of S.
Definition: alloc_model_funcs.hpp:34
T data(T...args)
Class to compute total for all possible allocation tensors while storing shared state between functi...
Definition: alloc_model_funcs.hpp:204
std::vector< Scalar > alpha
Parameter vector of Dirichlet prior for matrix of size .
Definition: alloc_model_params.hpp:37
double compute_second_term(const tensor_t< T, 3 > &S, const std::vector< Scalar > &beta)
Compute the second term of the sum when calculating log marginal of S.
Definition: alloc_model_funcs.hpp:91
Scalar a
Shape parameter of Gamma distribution.
Definition: alloc_model_params.hpp:28
Scalar b
Rate parameter of Gamma distribution.
Definition: alloc_model_params.hpp:32
double compute_third_term(const tensor_t< T, 3 > &S, Scalar a, Scalar b)
Compute the third term of the sum when calculating log marginal of S.
Definition: alloc_model_funcs.hpp:146
T size(T...args)
T begin(T...args)
double calc_marginal()
Calculate total marginal by calculating for every possible allocation tensor .
Definition: alloc_model_funcs.hpp:254
std::tuple< matrix_t< T >, matrix_t< T >, vector_t< T > > bnmf_priors(const shape< 3 > &tensor_shape, const Params< Scalar > &model_params)
Return prior matrices W, H and vector L according to the Bayesian NMF allocation model using distribu...
Definition: alloc_model_funcs.hpp:463
double log_marginal_S(const tensor_t< T, 3 > &S, const Params< Scalar > &model_params)
Compute the log marginal of tensor S with respect to the given distribution parameters.
Definition: alloc_model_funcs.hpp:574
std::vector< std::pair< size_t, size_t > > partition_change_indices(Integer n, Integer k)
Compute the sequence of indices to be incremented and decremented to generate all partitions of numbe...
Definition: util.hpp:560
T transform(T...args)
T accumulate(T...args)
std::vector< Scalar > beta
Parameter vector of Dirichlet prior for matrix of size .
Definition: alloc_model_params.hpp:42
Eigen::Matrix< Scalar, 1, Eigen::Dynamic, Eigen::RowMajor > vector_t
Vector type used in the computations.
Definition: defs.hpp:27
Main namespace for bnmf-algs library.
Definition: alloc_model_funcs.hpp:12
tensor_t< T, 3 > sample_S(const matrix_t< T > &prior_W, const matrix_t< T > &prior_H, const vector_t< T > &prior_L)
Sample a tensor S from generative Bayesian NMF model using the given priors.
Definition: alloc_model_funcs.hpp:528
double compute_fourth_term(const tensor_t< T, 3 > &S)
Compute the fourth term of the sum when calculating log marginal of S.
Definition: alloc_model_funcs.hpp:179