|
|
|
!(async function(){ |
|
var data = await util.getFile('mnist_train.csv') |
|
data.forEach(d => { |
|
delete d[''] |
|
d.i = +d.i |
|
}) |
|
|
|
var sel = d3.select('.umap-digit').html('') |
|
.at({role: 'graphics-document', 'aria-label': `Color coded UMAP of MNIST 1s showing that increasing privacy will misclassify slanted and serif β1β digits first.`}) |
|
|
|
var umapSel = sel.append('div') |
|
.append('div.chart-title').text('Sensitivity to higher privacy levels β') |
|
.parent() |
|
.st({maxWidth: 600, margin: '0 auto', marginBottom: 10}) |
|
.append('div') |
|
|
|
|
|
var buttonSel = sel.append('div.digit-button-container') |
|
.appendMany('div.button', d3.range(10)) |
|
.text(d => d) |
|
.on('click', d => drawDigitUmap(d)) |
|
|
|
|
|
drawDigitUmap(1) |
|
|
|
|
|
async function drawDigitUmap(digit){ |
|
buttonSel.classed('active', d => d == digit) |
|
|
|
|
|
var umap = await util.getFile(`cns-cache/umap_train_784_${digit}.npy`) |
|
util.getFile(`cns-cache/mnist_train_raw_${digit}.npy`) |
|
|
|
var digitData = data |
|
.filter(d => d.y == digit) |
|
.map((d, i) => ({ |
|
rawPos: [umap.data[i*2 + 0], umap.data[i*2 + 1]], |
|
priv_order: d.priv_order, |
|
y: d.y, |
|
i: d.i |
|
})) |
|
|
|
var c = d3.conventions({ |
|
sel: umapSel.html(''), |
|
width: 600, |
|
height: 600, |
|
layers: 'sdc', |
|
margin: {top: 45} |
|
}) |
|
|
|
var nTicks = 200 |
|
c.svg.appendMany('rect', d3.range(nTicks)) |
|
.at({ |
|
height: 15, |
|
width: 1, |
|
fill: i => d3.interpolatePlasma(i/nTicks), |
|
}) |
|
.translate(i => [c.width/2 - nTicks/2 - 20 + i, -c.margin.top + 5]) |
|
|
|
|
|
c.x.domain(d3.extent(digitData, d => d.rawPos[0])) |
|
c.y.domain(d3.extent(digitData, d => d.rawPos[1])) |
|
digitData.forEach(d => d.pos = [c.x(d.rawPos[0]), c.y(d.rawPos[1])]) |
|
|
|
c.sel.select('canvas').st({pointerEvents: 'none'}) |
|
var divSel = c.layers[1].st({pointerEvents: 'none'}) |
|
var ctx = c.layers[2] |
|
|
|
digitData.forEach(d => { |
|
ctx.beginPath() |
|
ctx.fillStyle = d3.interpolatePlasma(1 - d.priv_order/60000) |
|
ctx.rect(d.pos[0], d.pos[1], 2, 2) |
|
ctx.fill() |
|
}) |
|
|
|
var p = 10 |
|
c.svg |
|
.append('rect').at({width: c.width + p*2, height: c.height + p*2, x: -p, y: -p}) |
|
.parent() |
|
.call(d3.attachTooltip) |
|
.on('mousemove', function(){ |
|
var [px, py] = d3.mouse(this) |
|
|
|
var minPoint = _.minBy(digitData, d => { |
|
var dx = d.pos[0] - px |
|
var dy = d.pos[1] - py |
|
|
|
return dx*dx + dy*dy |
|
}) |
|
|
|
var s = 4 |
|
var c = d3.conventions({ |
|
sel: ttSel.html('').append('div'), |
|
width: 4*28, |
|
height: 4*28, |
|
layers: 'cs', |
|
margin: {top: 0, left: 0, right: 0, bottom: 0} |
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
ttSel.classed('tooltip-footnote', 0).st({width: 112}) |
|
|
|
util.drawDigit(c.layers[0], +minPoint.i, s) |
|
}) |
|
|
|
if (digit == 1){ |
|
var circleDigits = [ |
|
{r: 40, index: 1188}, |
|
{r: 53, index: 18698}, |
|
{r: 40, index: 1662} |
|
] |
|
circleDigits.forEach(d => { |
|
d.pos = digitData.filter(e => e.priv_order == d.index)[0].pos |
|
}) |
|
|
|
c.svg.append('g') |
|
.appendMany('g', circleDigits) |
|
.translate(d => d.pos) |
|
.append('circle') |
|
.at({r: d => d.r, fill: 'none', stroke: '#fff', strokeDasharray: '2 3', strokeWidth: 1}) |
|
|
|
var {r, pos} = circleDigits[0] |
|
|
|
|
|
divSel |
|
.append('div').translate(pos) |
|
.append('div').translate([r + 20, -r + 10]) |
|
.st({width: 150, fontWeight: 300, fontSize: 14, color: '#fff', xbackground: 'rgba(255,0,0,.2)', lineHeight: '1.2em'}) |
|
.text('Increasing privacy will misclassify slanted and serif β1β digits first') |
|
} |
|
} |
|
})() |
|
|
|
|
|
|