#ifndef _pyp_hh #define _pyp_hh #include "slice-sampler.h" #include #include #include //#include #include #include #include #include "log_add.h" #include "mt19937ar.h" // // Pitman-Yor process with customer and table tracking // template > class PYP : protected std::tr1::unordered_map //class PYP : protected google::sparse_hash_map { public: using std::tr1::unordered_map::const_iterator; using std::tr1::unordered_map::iterator; using std::tr1::unordered_map::begin; using std::tr1::unordered_map::end; // using google::sparse_hash_map::const_iterator; // using google::sparse_hash_map::iterator; // using google::sparse_hash_map::begin; // using google::sparse_hash_map::end; PYP(double a, double b, unsigned long seed = 0, Hash hash=Hash()); virtual int increment(Dish d, double p0); virtual int decrement(Dish d); // lookup functions int count(Dish d) const; double prob(Dish dish, double p0) const; double prob(Dish dish, double dcd, double dca, double dtd, double dta, double p0) const; double unnormalised_prob(Dish dish, double p0) const; int num_customers() const { return _total_customers; } int num_types() const { return std::tr1::unordered_map::size(); } //int num_types() const { return google::sparse_hash_map::size(); } bool empty() const { return _total_customers == 0; } double log_prob(Dish dish, double log_p0) const; // nb. d* are NOT logs double log_prob(Dish dish, double dcd, double dca, double dtd, double dta, double log_p0) const; int num_tables(Dish dish) const; int num_tables() const; double a() const { return _a; } void set_a(double a) { _a = a; } double b() const { return _b; } void set_b(double b) { _b = b; } virtual void clear(); std::ostream& debug_info(std::ostream& os) const; double log_restaurant_prob() const; double log_prior() const; static double log_prior_a(double a, double beta_a, double beta_b); static double log_prior_b(double b, double gamma_c, double gamma_s); template void resample_prior(Uniform01& rnd); template void resample_prior_a(Uniform01& rnd); template void resample_prior_b(Uniform01& rnd); protected: double _a, _b; // parameters of the Pitman-Yor distribution double _a_beta_a, _a_beta_b; // parameters of Beta prior on a double _b_gamma_s, _b_gamma_c; // parameters of Gamma prior on b struct TableCounter { TableCounter() : tables(0) {}; int tables; std::map table_histogram; // num customers at table -> number tables }; typedef std::tr1::unordered_map DishTableType; //typedef google::sparse_hash_map DishTableType; DishTableType _dish_tables; int _total_customers, _total_tables; typedef boost::mt19937 base_generator_type; typedef boost::uniform_real<> uni_dist_type; typedef boost::variate_generator gen_type; // uni_dist_type uni_dist; // base_generator_type rng; //this gets the seed // gen_type rnd; //instantiate: rnd(rng, uni_dist) //call: rnd() generates uniform on [0,1) // Function objects for calculating the parts of the log_prob for // the parameters a and b struct resample_a_type { int n, m; double b, a_beta_a, a_beta_b; const DishTableType& dish_tables; resample_a_type(int n, int m, double b, double a_beta_a, double a_beta_b, const DishTableType& dish_tables) : n(n), m(m), b(b), a_beta_a(a_beta_a), a_beta_b(a_beta_b), dish_tables(dish_tables) {} double operator() (double proposed_a) const { double log_prior = log_prior_a(proposed_a, a_beta_a, a_beta_b); double log_prob = 0.0; double lgamma1a = lgamma(1.0 - proposed_a); for (typename DishTableType::const_iterator dish_it=dish_tables.begin(); dish_it != dish_tables.end(); ++dish_it) for (std::map::const_iterator table_it=dish_it->second.table_histogram.begin(); table_it !=dish_it->second.table_histogram.end(); ++table_it) log_prob += (table_it->second * (lgamma(table_it->first - proposed_a) - lgamma1a)); log_prob += (proposed_a == 0.0 ? (m-1.0)*log(b) : ((m-1.0)*log(proposed_a) + lgamma((m-1.0) + b/proposed_a) - lgamma(b/proposed_a))); assert(std::isfinite(log_prob)); return log_prob + log_prior; } }; struct resample_b_type { int n, m; double a, b_gamma_c, b_gamma_s; resample_b_type(int n, int m, double a, double b_gamma_c, double b_gamma_s) : n(n), m(m), a(a), b_gamma_c(b_gamma_c), b_gamma_s(b_gamma_s) {} double operator() (double proposed_b) const { double log_prior = log_prior_b(proposed_b, b_gamma_c, b_gamma_s); double log_prob = 0.0; log_prob += (a == 0.0 ? (m-1.0)*log(proposed_b) : ((m-1.0)*log(a) + lgamma((m-1.0) + proposed_b/a) - lgamma(proposed_b/a))); log_prob += (lgamma(1.0+proposed_b) - lgamma(n+proposed_b)); return log_prob + log_prior; } }; /* lbetadist() returns the log probability density of x under a Beta(alpha,beta) * distribution. - copied from Mark Johnson's gammadist.c */ static long double lbetadist(long double x, long double alpha, long double beta); /* lgammadist() returns the log probability density of x under a Gamma(alpha,beta) * distribution - copied from Mark Johnson's gammadist.c */ static long double lgammadist(long double x, long double alpha, long double beta); }; template PYP::PYP(double a, double b, unsigned long seed, Hash) : std::tr1::unordered_map(10), _a(a), _b(b), //: google::sparse_hash_map(10), _a(a), _b(b), _a_beta_a(1), _a_beta_b(1), _b_gamma_s(1), _b_gamma_c(1), //_a_beta_a(1), _a_beta_b(1), _b_gamma_s(10), _b_gamma_c(0.1), _total_customers(0), _total_tables(0)//, //uni_dist(0,1), rng(seed == 0 ? (unsigned long)this : seed), rnd(rng, uni_dist) { // std::cerr << "\t##PYP::PYP(a=" << _a << ",b=" << _b << ")" << std::endl; //set_deleted_key(-std::numeric_limits::max()); } template double PYP::prob(Dish dish, double p0) const { int c = count(dish), t = num_tables(dish); double r = num_tables() * _a + _b; //std::cerr << "\t\t\t\tPYP::prob(" << dish << "," << p0 << ") c=" << c << " r=" << r << std::endl; if (c > 0) return (c - _a * t + r * p0) / (num_customers() + _b); else return r * p0 / (num_customers() + _b); } template double PYP::unnormalised_prob(Dish dish, double p0) const { int c = count(dish), t = num_tables(dish); double r = num_tables() * _a + _b; if (c > 0) return (c - _a * t + r * p0); else return r * p0; } template double PYP::prob(Dish dish, double dcd, double dca, double dtd, double dta, double p0) const { int c = count(dish) + dcd, t = num_tables(dish) + dtd; double r = (num_tables() + dta) * _a + _b; if (c > 0) return (c - _a * t + r * p0) / (num_customers() + dca + _b); else return r * p0 / (num_customers() + dca + _b); } template double PYP::log_prob(Dish dish, double log_p0) const { using std::log; int c = count(dish), t = num_tables(dish); double r = log(num_tables() * _a + b); if (c > 0) return Log::add(log(c - _a * t), r + log_p0) - log(num_customers() + _b); else return r + log_p0 - log(num_customers() + b); } template double PYP::log_prob(Dish dish, double dcd, double dca, double dtd, double dta, double log_p0) const { using std::log; int c = count(dish) + dcd, t = num_tables(dish) + dtd; double r = log((num_tables() + dta) * _a + b); if (c > 0) return Log::add(log(c - _a * t), r + log_p0) - log(num_customers() + dca + _b); else return r + log_p0 - log(num_customers() + dca + b); } template int PYP::increment(Dish dish, double p0) { int delta = 0; TableCounter &tc = _dish_tables[dish]; // seated on a new or existing table? int c = count(dish), t = num_tables(dish), T = num_tables(); double pshare = (c > 0) ? (c - _a*t) : 0.0; double pnew = (_b + _a*T) * p0; assert (pshare >= 0.0); //assert (pnew > 0.0); //if (rnd() < pnew / (pshare + pnew)) { if (mt_genrand_res53() < pnew / (pshare + pnew)) { // assign to a new table tc.tables += 1; tc.table_histogram[1] += 1; _total_tables += 1; delta = 1; } else { // randomly assign to an existing table // remove constant denominator from inner loop //double r = rnd() * (c - _a*t); double r = mt_genrand_res53() * (c - _a*t); for (std::map::iterator hit = tc.table_histogram.begin(); hit != tc.table_histogram.end(); ++hit) { r -= ((hit->first - _a) * hit->second); if (r <= 0) { tc.table_histogram[hit->first+1] += 1; hit->second -= 1; if (hit->second == 0) tc.table_histogram.erase(hit); break; } } if (r > 0) { std::cerr << r << " " << c << " " << _a << " " << t << std::endl; assert(false); } delta = 0; } std::tr1::unordered_map::operator[](dish) += 1; //google::sparse_hash_map::operator[](dish) += 1; _total_customers += 1; return delta; } template int PYP::count(Dish dish) const { typename std::tr1::unordered_map::const_iterator //typename google::sparse_hash_map::const_iterator dcit = find(dish); if (dcit != end()) return dcit->second; else return 0; } template int PYP::decrement(Dish dish) { typename std::tr1::unordered_map::iterator dcit = find(dish); //typename google::sparse_hash_map::iterator dcit = find(dish); if (dcit == end()) { std::cerr << dish << std::endl; assert(false); } int delta = 0; typename std::tr1::unordered_map::iterator dtit = _dish_tables.find(dish); //typename google::sparse_hash_map::iterator dtit = _dish_tables.find(dish); if (dtit == _dish_tables.end()) { std::cerr << dish << std::endl; assert(false); } TableCounter &tc = dtit->second; //std::cerr << "\tdecrement for " << dish << "\n"; //std::cerr << "\tBEFORE histogram: " << tc.table_histogram << " "; //std::cerr << "count: " << count(dish) << " "; //std::cerr << "tables: " << tc.tables << "\n"; //double r = rnd() * count(dish); double r = mt_genrand_res53() * count(dish); for (std::map::iterator hit = tc.table_histogram.begin(); hit != tc.table_histogram.end(); ++hit) { //r -= (hit->first - _a) * hit->second; r -= (hit->first) * hit->second; if (r <= 0) { if (hit->first > 1) tc.table_histogram[hit->first-1] += 1; else { delta = -1; tc.tables -= 1; _total_tables -= 1; } hit->second -= 1; if (hit->second == 0) tc.table_histogram.erase(hit); break; } } if (r > 0) { std::cerr << r << " " << count(dish) << " " << _a << " " << num_tables(dish) << std::endl; assert(false); } // remove the customer dcit->second -= 1; _total_customers -= 1; assert(dcit->second >= 0); if (dcit->second == 0) { erase(dcit); _dish_tables.erase(dtit); //std::cerr << "\tAFTER histogram: Empty\n"; } else { //std::cerr << "\tAFTER histogram: " << _dish_tables[dish].table_histogram << " "; //std::cerr << "count: " << count(dish) << " "; //std::cerr << "tables: " << _dish_tables[dish].tables << "\n"; } return delta; } template int PYP::num_tables(Dish dish) const { typename std::tr1::unordered_map::const_iterator //typename google::sparse_hash_map::const_iterator dtit = _dish_tables.find(dish); //assert(dtit != _dish_tables.end()); if (dtit == _dish_tables.end()) return 0; return dtit->second.tables; } template int PYP::num_tables() const { return _total_tables; } template std::ostream& PYP::debug_info(std::ostream& os) const { int hists = 0, tables = 0; for (typename std::tr1::unordered_map::const_iterator //for (typename google::sparse_hash_map::const_iterator dtit = _dish_tables.begin(); dtit != _dish_tables.end(); ++dtit) { hists += dtit->second.table_histogram.size(); tables += dtit->second.tables; // if (dtit->second.tables <= 0) // std::cerr << dtit->first << " " << count(dtit->first) << std::endl; assert(dtit->second.tables > 0); assert(!dtit->second.table_histogram.empty()); // os << "Dish " << dtit->first << " has " << count(dtit->first) << " customers, and is sitting at " << dtit->second.tables << " tables.\n"; for (std::map::const_iterator hit = dtit->second.table_histogram.begin(); hit != dtit->second.table_histogram.end(); ++hit) { // os << " " << hit->second << " tables with " << hit->first << " customers." << std::endl; assert(hit->second > 0); } } os << "restaurant has " << _total_customers << " customers; " << _total_tables << " tables; " << tables << " tables'; " << num_types() << " dishes; " << _dish_tables.size() << " dishes'; and " << hists << " histogram entries\n"; return os; } template void PYP::clear() { this->std::tr1::unordered_map::clear(); //this->google::sparse_hash_map::clear(); _dish_tables.clear(); _total_tables = _total_customers = 0; } // log_restaurant_prob returns the log probability of the PYP table configuration. // Excludes Hierarchical P0 term which must be calculated separately. template double PYP::log_restaurant_prob() const { if (_total_customers < 1) return (double)0.0; double log_prob = 0.0; double lgamma1a = lgamma(1.0-_a); //std::cerr << "-------------------\n" << std::endl; for (typename DishTableType::const_iterator dish_it=_dish_tables.begin(); dish_it != _dish_tables.end(); ++dish_it) { for (std::map::const_iterator table_it=dish_it->second.table_histogram.begin(); table_it !=dish_it->second.table_histogram.end(); ++table_it) { log_prob += (table_it->second * (lgamma(table_it->first - _a) - lgamma1a)); //std::cerr << "|" << dish_it->first->parent << " --> " << dish_it->first->rhs << " " << table_it->first << " " << table_it->second << " " << log_prob; } } //std::cerr << std::endl; log_prob += (_a == (double)0.0 ? (_total_tables-1.0)*log(_b) : (_total_tables-1.0)*log(_a) + lgamma((_total_tables-1.0) + _b/_a) - lgamma(_b/_a)); //std::cerr << "\t\t" << log_prob << std::endl; log_prob += (lgamma(1.0 + _b) - lgamma(_total_customers + _b)); //std::cerr << _total_customers << " " << _total_tables << " " << log_prob << " " << log_prior() << std::endl; //std::cerr << _a << " " << _b << std::endl; if (!std::isfinite(log_prob)) { assert(false); } //return log_prob; if (log_prob > 0.0) std::cerr << log_prob << std::endl; return log_prob;// + log_prior(); } template double PYP::log_prior() const { double prior = 0.0; if (_a_beta_a > 0.0 && _a_beta_b > 0.0 && _a > 0.0) prior += log_prior_a(_a, _a_beta_a, _a_beta_b); if (_b_gamma_s > 0.0 && _b_gamma_c > 0.0) prior += log_prior_b(_b, _b_gamma_c, _b_gamma_s); return prior; } template double PYP::log_prior_a(double a, double beta_a, double beta_b) { return lbetadist(a, beta_a, beta_b); } template double PYP::log_prior_b(double b, double gamma_c, double gamma_s) { return lgammadist(b, gamma_c, gamma_s); } template long double PYP::lbetadist(long double x, long double alpha, long double beta) { assert(x > 0); assert(x < 1); assert(alpha > 0); assert(beta > 0); return (alpha-1)*log(x)+(beta-1)*log(1-x)+lgamma(alpha+beta)-lgamma(alpha)-lgamma(beta); //boost::math::lgamma } template long double PYP::lgammadist(long double x, long double alpha, long double beta) { assert(alpha > 0); assert(beta > 0); return (alpha-1)*log(x) - alpha*log(beta) - x/beta - lgamma(alpha); } template template void PYP::resample_prior(Uniform01& rnd) { for (int num_its=5; num_its >= 0; --num_its) { resample_prior_b(rnd); resample_prior_a(rnd); } resample_prior_b(rnd); } template template void PYP::resample_prior_b(Uniform01& rnd) { if (_total_tables == 0) return; //int niterations = 10; // number of resampling iterations int niterations = 5; // number of resampling iterations //std::cerr << "\n## resample_prior_b(), initial a = " << _a << ", b = " << _b << std::endl; resample_b_type b_log_prob(_total_customers, _total_tables, _a, _b_gamma_c, _b_gamma_s); _b = slice_sampler1d(b_log_prob, _b, rnd, (double) 0.0, std::numeric_limits::infinity(), //_b = slice_sampler1d(b_log_prob, _b, mt_genrand_res53, (double) 0.0, std::numeric_limits::infinity(), (double) 0.0, niterations, 100*niterations); //std::cerr << "\n## resample_prior_b(), final a = " << _a << ", b = " << _b << std::endl; } template template void PYP::resample_prior_a(Uniform01& rnd) { if (_total_tables == 0) return; //int niterations = 10; int niterations = 5; //std::cerr << "\n## Initial a = " << _a << ", b = " << _b << std::endl; resample_a_type a_log_prob(_total_customers, _total_tables, _b, _a_beta_a, _a_beta_b, _dish_tables); _a = slice_sampler1d(a_log_prob, _a, rnd, std::numeric_limits::min(), //_a = slice_sampler1d(a_log_prob, _a, mt_genrand_res53, std::numeric_limits::min(), (double) 1.0, (double) 0.0, niterations, 100*niterations); } #endif