MCMC Sampling
Math
Statistics
Hexbin
Code
chart = {
const width = fullWidth,
height = fullHeight;
const window = d3.create("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${fullWidth} ${fullHeight}`)
.attr("preserveAspectRatio", "xMidYMid meet");
const svg = window.append("g")
.attr("class", "mcmc");
svg.append("defs")
.append("clipPath")
.attr("id", "def-mcmc-clipPath")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "white")
.style("stroke", "none");
svg.append("g").attr("clip-path", "url(#def-mcmc-clipPath)")
.attr("class", "hexbins");
svg.append("g").attr("clip-path", "url(#def-mcmc-clipPath)")
.attr("class", "contours");
svg.append("g").attr("clip-path", "url(#def-mcmc-clipPath)")
.attr("class", "walkers")
let xc, yc, logLikelihood, abortController;
const projection = d3.geoIdentity();
const numBins = 100;
const x2grid = d3.scaleLinear().range([0, numBins - 1]),
y2grid = d3.scaleLinear().range([0, numBins - 1]);
const hexbin = d3.hexbin()
.x(d => projection([x2grid(d[0]), y2grid(d[1])])[0])
.y(d => projection([x2grid(d[0]), y2grid(d[1])])[1])
.radius(5)
.extent([[0, 0], [width, height]])
const color = d3.scaleSequential(d3.interpolateYlGn);
const data = {
rosenbrock: {
x0: -2,
x1: 2,
y0: -2,
y1: 3,
z: [-60, -30, -20, -10],
logLikelihood: d =>
-(100 * Math.pow(d[1] - Math.pow(d[0], 2), 2) + Math.pow(d[0] - 1, 2)),
},
booth: {
x0: 1-4,
x1: 1+4,
y0: 3-4,
y1: 3+4,
z: [-20, -15, -10, -5],
logLikelihood: d =>
-(Math.pow(d[0] + 2 * d[1] - 7, 2) + Math.pow(2 * d[0] + d[1] - 5, 2)),
}
};
function linspace(domain, n){
const dx = (domain[1] - domain[0]) / (n - 1)
return d3.range(n).map(i => domain[0] + i * dx);
}
function change(value) {
abortController?.abort();
xc = 0;
yc = 1;
const d = data[value];
logLikelihood = d.logLikelihood;
svg.selectAll(".contour")
.remove();
x2grid.domain([d.x0, d.x1]);
y2grid.domain([d.y0, d.y1]);
const values = new Array(numBins * numBins);
const x = linspace(x2grid.domain(), numBins);
const y = linspace(y2grid.domain(), numBins);
for (let j = 0, k = 0; j < numBins; ++j) {
for (let i = 0; i < numBins; ++i, ++k) {
values[k] = logLikelihood([x[i], y[j]]);
}
}
const contours = d3.contours()
.size([numBins, numBins])
.thresholds(d.z)
(values);
projection.fitSize([width, height], contours[0]);
svg.select(".contours").selectAll(".contour")
.data(contours.slice(1))
.join("path")
.attr("class", "contour")
.attr("fill", "none")
.attr("stroke", "#C0C0C0")
.attr("d", d3.geoPath(projection));
start();
}
function start() {
svg.selectAll(".walker")
.remove();
svg.selectAll(".hexbin")
.remove();
const numWalkers = 100,
numIterations = 10000,
delay = 100;
const initialPosition = d3.range(numWalkers)
.map(() => [xc + (Math.random() - 0.5), yc + (Math.random() - 0.5)]);
abortController = new AbortController();
const sample = MCMC(logLikelihood, initialPosition, numIterations)
.delay(delay)
.callback(render)
.signal(abortController.signal);
sample(d3.interval).then((sampler) => {
console.log(`a = ${sampler.getAcceptanceFraction().toFixed(2)}`);
});
}
function render(result) {
svg.select(".walkers").selectAll(".walker")
.data(result.iteration)
.join("circle")
.attr("class", "walker")
.attr("r", 1.5)
.attr("fill", "black")
.attr("stroke", "none")
.attr("cx", d => projection([x2grid(d[0]), y2grid(d[1])])[0])
.attr("cy", d => projection([x2grid(d[0]), y2grid(d[1])])[1]);
const bins = hexbin(result.chain);
color.domain([0, d3.max(bins, d => d.length)]);
svg.select(".hexbins").selectAll(".hexbin")
.data(bins)
.join("path")
.attr("class", "hexbin")
.attr("d", hexbin.hexagon())
.attr("transform", d => `translate(${d.x},${d.y})`)
.attr("fill", d => color(d.length));
}
svg.on("click", function(event) {
abortController.abort();
const pointer = projection.invert(d3.pointer(event, this));
xc = x2grid.invert(pointer[0]);
yc = y2grid.invert(pointer[1]);
start();
});
invalidation.then(() => {
abortController.abort();
});
return Object.assign(window.node(), {
update(values) {
change(values.function);
},
});
}Affine invariant MCMC sampling is a popular algorithm for sampling hard-to-sample probability density functions, since it works well in many cases without much fine-tuning, using an ensemble of walkers (shown as black points).