One popular method for estimating statistical models is sampling, and in particular Gibbs sampling. As I’ve written before, sampling is based on randomness, which makes it somewhat difficult to debug.
On the other hand, one popular way to ensure the quality of code is through arrays of unit tests, which are designed to test your code at a very fine-grained level (e.g. one test for each function in the code base). However, because sampling is by nature random, the output of a sampling algorithm can be expected to change every time you run it, and thus it is not trivial to write unit tests. This article shows two simple ways that I’ve used to overcome this difficulty and write unit tests for sampling algorithms.
First, let’s assume that we have a sampling function called SampleMultinomial, which samples a single value from a multinomial distribution, defined by an array of doubles. In the program below, we sample from the distribution {0.5, 0.3, 0.2}, so we should expect the program to print “0″ 50% of the time, “1″ 30% of the time, and “2″ 20% of the time.
#include <vector>
#include <iostream>
#include <cstdlib>
using namespace std;
int SampleMultinomial(const vector<double> & distribution) {
double value = (double)rand()/RAND_MAX;
for(int i = 0; i < distribution.size(); i++)
if( (value -= distribution[i]) <= 0 )
return i;
cerr << "Overflow in SampleMultinomial()" << endl;
return -1;
}
int main() {
srand( time(NULL) );
vector<double> my_distribution(3);
my_distribution[0] = 0.5;
my_distribution[1] = 0.3;
my_distribution[2] = 0.2;
cout << SampleMultinomial(my_distribution) << endl;
return 0;
}
Now, how do we test this function? We can do so by utilizing the fact that sampling enough times will help us recover the true probability distribution (the law of large numbers), or we can utilize the fact that the “random” numbers that the computer is generating are actually pseudo-random.
Utilizing the Law of Large Numbers
The original motivation for sampling is that if we take enough samples, we can approximate the true distribution. In our previous example, this means that if we take the count of the number of times that SampleMultinomial returns “0″ (c(x=”0″), and divide it by the total number of times, that we called SampleMultinomial (C), this will approach the true probability of “0″ (p(x=”0″)), as C approaches infinity:
Now, let’s try to write this in code. We do this by writing a loop that calls SampleMultinomial “C” times, and counts the number of times we get each result. Finally, for each of “x=0″ “x=1″ and “x=2″, we check that its probability is sufficiently close to the true expected probability.
bool TestLawOfLargeNumbers(const vector<double> & distribution) {
// Call SampleMultinomial C times, and count the number of times we get
// each result
int C = 1000000;
vector<int> counts(distribution.size(), 0);
for(int i = 0; i < C; i++) {
int x = SampleMultinomial(distribution);
counts[x]++;
}
// Check to make sure that the value of the probabilities match the true
// probabilities of the distribution within a confidence threshold
double confidence = 0.005;
bool passed = true;
for(int i = 0; i < distribution.size(); i++) {
double estimated_probability = (double)counts[i]/C;
if(abs(distribution[i] - estimated_probability) > confidence) {
cerr << "FAILED: Difference between at " << i << " ("
<< abs(distribution[i] - estimated_probability)
<< ") is more than confidence " << confidence << endl;
passed = false;
}
}
return passed;
}
It should be noted that this works for discrete distributions where we can explicitly count the number of times each instance is generated. If we are handling continuous distributions, it may be possible to handle them in this way by measuring the Kullback-Leibler divergence from the true distribution, or quantizing the distribution, but I have never tried these.
Utilizing Pseudo-Randomness
Using the law of large numbers relatively simple to implement, and can be used without any knowledge of the internals of the algorithm. On the other hand, when generating samples is expensive, it may be relatively slow to acquire enough samples to ensure that we will get close enough to the true distribution. Also, as mentioned above, continuous distributions are not easy to handle. An alternative method that is useful in these cases is to utilize pseudo-randomness to ensure that we will get the same result every time, then use the expected result to test our algorithm.
To do so, we first set the random seed of our pseudo-random number generator to a specific value, say 1234, and generate as many numbers as we need in sequence.
int main() {
srand( 1234 );
const int C = 10;
for(int i = 0; i < C; i++)
cout << (double)rand()/RAND_MAX << endl;
return 0;
}
This gives us an output of:
0.223118 0.216796 0.447559 0.492617 0.569365 0.473787 0.474919 0.0336593 0.954041 0.958502
Given these random numbers, and a grasp of how the sampling algorithm works, we can estimate the results of the sampling algorithm based on these numbers.* For example given an array {0.5, 0.3, 0.2}, SampleMultinomial should convert all random numbers [0.0,0.5] into “0,” numbers (0.5,0.8] into “1,” and numbers (0.8,1.0] into “2.” Using this fact, we manually create an array of answers that corresponds to our random seeds, and create a test function that checks to make sure that these are actually the values that our sampling algorithm produces.
bool TestPseudoRandom(const vector<double> & distribution) {
srand( 1234 );
const int C = 10;
int expected[C] = { 0, 0, 0, 0, 1, 0, 0, 0, 2, 2 };
bool passed = true;
for(int i = 0; i < C; i++) {
int x = SampleMultinomial(distribution);
if(x != expected[i]) {
cerr << "FAILED: Actual value at " << i << " (" << x
<< ") not equal to expected (" << expected[i] << ")" << endl;
passed = false;
}
}
return passed;
}
If you’d like to try this yourself, I’ve made the full code available here.
*One thing we need to be careful here is that this is also relying on the fact that the random number generator will generate the same numbers given a particular seed, which may not be true for some languages or operating systems.







