// MetropolisHastings generates rows(batch) samples using the Metropolis Hastings // algorithm (http://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm), // with the given target and proposal distributions, starting at the intial location // and storing the results in-place into samples. If src != nil, it will be used to generate random // numbers, otherwise rand.Float64 will be used. // // Metropolis-Hastings is a Markov-chain Monte Carlo algorithm that generates // samples according to the distribution specified by target by using the Markov // chain implicitly defined by the proposal distribution. At each // iteration, a proposal point is generated randomly from the current location. // This proposal point is accepted with probability // p = min(1, (target(new) * proposal(current|new)) / (target(current) * proposal(new|current))) // If the new location is accepted, it is stored into batch and becomes the // new current location. If it is rejected, the current location remains and // is stored into samples. Thus, a location is stored into batch at every iteration. // // The samples in Metropolis Hastings are correlated with one another through the // Markov chain. As a result, the initial value can have a significant influence // on the early samples, and so, typically, the first samples generated by the chain // are ignored. This is known as "burn-in", and can be accomplished with slicing. // The best choice for burn-in length will depend on the sampling and target // distributions. // // Many choose to have a sampling "rate" where a number of samples // are ignored in between each kept sample. This helps decorrelate // the samples from one another, but also reduces the number of available samples. // A sampling rate can be implemented with successive calls to MetropolisHastings. func MetropolisHastings(batch *mat64.Dense, initial []float64, target distmv.LogProber, proposal MHProposal, src *rand.Rand) { f64 := rand.Float64 if src != nil { f64 = src.Float64 } if len(initial) == 0 { panic("metropolishastings: zero length initial") } r, _ := batch.Dims() current := make([]float64, len(initial)) copy(current, initial) proposed := make([]float64, len(initial)) currentLogProb := target.LogProb(initial) for i := 0; i < r; i++ { proposal.ConditionalRand(proposed, current) proposedLogProb := target.LogProb(proposed) probTo := proposal.ConditionalLogProb(proposed, current) probBack := proposal.ConditionalLogProb(current, proposed) accept := math.Exp(proposedLogProb + probBack - probTo - currentLogProb) if accept > f64() { copy(current, proposed) currentLogProb = proposedLogProb } batch.SetRow(i, current) } }
// Sample generates rows(batch) samples using the Metropolis Hastings sample // generation method. The initial location is NOT updated during the call to Sample. // // The number of columns in batch must equal len(m.Initial), otherwise Sample // will panic. func (m MetropolisHastingser) Sample(batch *mat64.Dense) { burnIn := m.BurnIn rate := m.Rate if rate == 0 { rate = 1 } r, c := batch.Dims() if len(m.Initial) != c { panic("metropolishastings: length mismatch") } // Use the optimal size for the temporary memory to allow the fewest calls // to MetropolisHastings. The case where tmp shadows samples must be // aligned with the logic after burn-in so that tmp does not shadow samples // during the rate portion. tmp := batch if rate > r { tmp = mat64.NewDense(rate, c, nil) } rTmp, _ := tmp.Dims() // Perform burn-in. remaining := burnIn initial := make([]float64, c) copy(initial, m.Initial) for remaining != 0 { newSamp := min(rTmp, remaining) MetropolisHastings(tmp.View(0, 0, newSamp, c).(*mat64.Dense), initial, m.Target, m.Proposal, m.Src) copy(initial, tmp.RawRowView(newSamp-1)) remaining -= newSamp } if rate == 1 { MetropolisHastings(batch, initial, m.Target, m.Proposal, m.Src) return } if rTmp <= r { tmp = mat64.NewDense(rate, c, nil) } // Take a single sample from the chain. MetropolisHastings(batch.View(0, 0, 1, c).(*mat64.Dense), initial, m.Target, m.Proposal, m.Src) copy(initial, batch.RawRowView(0)) // For all of the other samples, first generate Rate samples and then actually // accept the last one. for i := 1; i < r; i++ { MetropolisHastings(tmp, initial, m.Target, m.Proposal, m.Src) v := tmp.RawRowView(rate - 1) batch.SetRow(i, v) copy(initial, v) } }
// Rejection generates rows(batch) samples using the rejection sampling algorithm and // stores them in place into samples. // Sampling continues until batch is filled. Rejection returns the total number of proposed // locations and a boolean indicating if the rejection sampling assumption is // violated (see details below). If the returned boolean is false, all elements // of samples are set to NaN. If src != nil, it will be used to generate random // numbers, otherwise rand.Float64 will be used. // // Rejection sampling generates points from the target distribution by using // the proposal distribution. At each step of the algorithm, the proposaed point // is accepted with probability // p = target(x) / (proposal(x) * c) // where target(x) is the probability of the point according to the target distribution // and proposal(x) is the probability according to the proposal distribution. // The constant c must be chosen such that target(x) < proposal(x) * c for all x. // The expected number of proposed samples is len(samples) * c. // // Target may return the true (log of) the probablity of the location, or it may return // a value that is proportional to the probability (logprob + constant). This is // useful for cases where the probability distribution is only known up to a normalization // constant. func Rejection(batch *mat64.Dense, target distmv.LogProber, proposal distmv.RandLogProber, c float64, src *rand.Rand) (nProposed int, ok bool) { if c < 1 { panic("rejection: acceptance constant must be greater than 1") } f64 := rand.Float64 if src != nil { f64 = src.Float64 } r, dim := batch.Dims() v := make([]float64, dim) var idx int for { nProposed++ proposal.Rand(v) qx := proposal.LogProb(v) px := target.LogProb(v) accept := math.Exp(px-qx) / c if accept > 1 { // Invalidate the whole result and return a failure. for i := 0; i < r; i++ { for j := 0; j < dim; j++ { batch.Set(i, j, math.NaN()) } } return nProposed, false } if accept > f64() { batch.SetRow(idx, v) idx++ if idx == r { break } } } return nProposed, true }