<!-- DP-SGD Privacy Budget Demo â adjust clipping threshold C and noise multiplier sigma -->
<div class="controls">
<label>
<span>Clip threshold <em>C</em></span>
<input id="clip" type="range" min="0.1" max="2" step="0.1" value="1">
<span id="clipVal" class="val">1.0</span>
</label>
<label>
<span>Noise multiplier <em>σ</em></span>
<input id="noise" type="range" min="0.5" max="3" step="0.1" value="1.1">
<span id="noiseVal" class="val">1.1</span>
</label>
<label>
<span>Batch size</span>
<input id="batch" type="range" min="16" max="256" step="16" value="64">
<span id="batchVal" class="val">64</span>
</label>
<label>
<span>Dataset size</span>
<input id="ndata" type="range" min="500" max="5000" step="500" value="1000">
<span id="ndataVal" class="val">1000</span>
</label>
</div>
<div class="btns">
<button id="btnStep" type="button">Step one epoch</button>
<button id="btnRun" type="button">Run training</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div class="info-row">
<span class="badge" id="epsBadge">ϵ = 0.00</span>
<span class="badge sec" id="epochBadge">Epoch 0</span>
<span class="badge sec" id="deltaBadge">δ = 1e-5</span>
</div>
<canvas id="chart" width="480" height="200"></canvas>
<p id="verdict" class="verdict"></p>
/* Layout and colour for the DP-SGD privacy budget visualiser */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem 1rem; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .4rem; font-size: .85rem; }
label span:first-child { min-width: 7rem; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.val { min-width: 2.8rem; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.btns { display: flex; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.info-row { display: flex; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
.badge { font: 700 13px ui-monospace, monospace; padding: .25rem .55rem;
border-radius: 6px; background: #e63946; color: #fff; }
.badge.sec { background: #1d3557; }
canvas { width: 100%; border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.verdict { font-size: .9rem; font-weight: 600; margin: .45rem 0 0; min-height: 1.3em; }
.verdict.good { color: #0a7d33; }
.verdict.warn { color: #b45a00; }
.verdict.bad { color: #c92f3c; }
/* DP-SGD privacy budget simulator â Renyi DP moments accountant (simplified) */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* training history â one epsilon value per step */
let history = [];
let animId = null;
/* parameter controls */
const $ = id => document.getElementById(id);
const clip = $('clip');
const noise = $('noise');
const batch = $('batch');
const ndata = $('ndata');
function getParams() {
return {
C: parseFloat(clip.value),
sigma: parseFloat(noise.value),
q: parseInt(batch.value, 10) / parseInt(ndata.value, 10),
delta: 1e-5
};
}
/* Renyi DP per-step cost (simplified Gaussian mechanism with subsampling) */
function rdpStep(alpha, C, sigma, q) {
/* per-step RDP cost for Gaussian mechanism with subsampling rate q */
const sens = 1.0;
const base = alpha / (2 * sigma * sigma) * sens * sens;
return q * q * base;
}
/* convert accumulated RDP to (epsilon, delta)-DP */
function epsilonFromRdp(rdpEps, alpha, delta) {
/* RDP to approx (eps, delta)-DP via standard conversion */
return rdpEps + Math.log(1 / delta) / (alpha - 1);
}
function computeEpsilon(steps, C, sigma, q, delta) {
/* minimise over Renyi orders alpha to get the tightest epsilon */
const alphas = [2, 4, 8, 16, 32, 64];
let best = Infinity;
for (const alpha of alphas) {
const rdp = steps * rdpStep(alpha, C, sigma, q);
const eps = epsilonFromRdp(rdp, alpha, delta);
if (eps < best) best = eps;
}
return best;
}
const STEPS_PER_EPOCH = 50;
function currentEpoch() { return Math.floor(history.length / STEPS_PER_EPOCH); }
function stepOnce() {
const { C, sigma, q, delta } = getParams();
const step = history.length + 1;
const eps = computeEpsilon(step, C, sigma, q, delta);
history.push(eps);
draw();
updateBadges();
}
function draw() {
const canvas = $('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
/* draw chart axes with padding */
const pad = { t: 14, r: 12, b: 32, l: 42 };
const iW = W - pad.l - pad.r;
const iH = H - pad.t - pad.b;
const maxSteps = Math.max(history.length, STEPS_PER_EPOCH);
const maxEps = Math.max(...history, 1, 10);
/* horizontal grid lines with y-axis labels */
ctx.strokeStyle = '#e0e6ec';
ctx.lineWidth = 1;
for (let i = 0; i <= 4; i++) {
const y = pad.t + (iH * i / 4);
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + iW, y); ctx.stroke();
ctx.fillStyle = '#666'; ctx.font = '10px system-ui'; ctx.textAlign = 'right';
ctx.fillText((maxEps * (1 - i / 4)).toFixed(1), pad.l - 4, y + 3);
}
/* x-axis epoch labels */
ctx.fillStyle = '#666'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
for (let e = 0; e <= Math.ceil(maxSteps / STEPS_PER_EPOCH); e++) {
const x = pad.l + (e * STEPS_PER_EPOCH / maxSteps) * iW;
ctx.fillText(String(e), x, H - pad.b + 14);
}
ctx.fillStyle = '#444'; ctx.font = '11px system-ui';
ctx.fillText('Epoch', pad.l + iW / 2, H - 2);
/* rotated y-axis label */
ctx.save(); ctx.translate(11, pad.t + iH / 2); ctx.rotate(-Math.PI / 2);
ctx.textAlign = 'center'; ctx.fillText('Δ (privacy cost)', 0, 0); ctx.restore();
/* reference epsilon thresholds (strong / moderate / weak) */
const thresholds = [{ v: 1, label: 'Δ=1', col: '#0a7d33' }, { v: 8, label: 'Δ=8', col: '#b45a00' }, { v: 20, label: 'Δ=20', col: '#c92f3c' }];
for (const t of thresholds) {
if (t.v > maxEps * 1.05) continue;
const y = pad.t + iH * (1 - t.v / maxEps);
ctx.strokeStyle = t.col; ctx.setLineDash([4, 3]); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + iW, y); ctx.stroke();
ctx.fillStyle = t.col; ctx.font = '9px system-ui'; ctx.textAlign = 'left';
ctx.fillText(t.label, pad.l + 2, y - 2);
}
ctx.setLineDash([]);
/* draw the epsilon curve */
if (history.length > 0) {
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
ctx.beginPath();
history.forEach((eps, i) => {
const x = pad.l + ((i + 1) / maxSteps) * iW;
const y = pad.t + iH * (1 - Math.min(eps, maxEps) / maxEps);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
}
function updateBadges() {
const eps = history.length ? history[history.length - 1] : 0;
$('epsBadge').textContent = 'Δ = ' + eps.toFixed(2);
$('epochBadge').textContent = 'Epoch ' + currentEpoch();
const v = $('verdict');
if (history.length === 0) { v.textContent = ''; v.className = 'verdict'; return; }
if (eps <= 1) { v.textContent = i18nFmt('Δ = %eps% â strong privacy. Model leaks very little.', { eps: eps.toFixed(2) }); v.className = 'verdict good'; }
else if (eps <= 8) { v.textContent = i18nFmt('Δ = %eps% â moderate privacy. Acceptable for many real deployments.', { eps: eps.toFixed(2) }); v.className = 'verdict warn'; }
else { v.textContent = i18nFmt('Δ = %eps% â weak privacy. Budget nearly exhausted; consider more noise.', { eps: eps.toFixed(2) }); v.className = 'verdict bad'; }
}
function syncLabels() {
$('clipVal').textContent = parseFloat(clip.value).toFixed(1);
$('noiseVal').textContent = parseFloat(noise.value).toFixed(1);
$('batchVal').textContent = batch.value;
$('ndataVal').textContent = ndata.value;
}
function reset() {
if (animId) { clearInterval(animId); animId = null; }
history = [];
const canvas = $('chart');
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
draw();
updateBadges();
$('btnRun').textContent = 'Run training';
}
function runAuto() {
if (animId) {
clearInterval(animId); animId = null;
$('btnRun').textContent = 'Run training';
return;
}
$('btnRun').textContent = 'Pause';
animId = setInterval(() => {
stepOnce();
if (history.length >= STEPS_PER_EPOCH * 10) {
clearInterval(animId); animId = null;
$('btnRun').textContent = 'Run training';
}
}, 60);
}
$('btnStep').addEventListener('click', stepOnce);
$('btnRun').addEventListener('click', runAuto);
$('btnReset').addEventListener('click', reset);
[clip, noise, batch, ndata].forEach(el => {
el.addEventListener('input', () => { syncLabels(); reset(); });
});
syncLabels();
draw();
<!-- DP-SGD Privacy Budget Demo â adjust clipping threshold C and noise multiplier sigma -->
<div class="controls">
<label>
<span>Clip threshold <em>C</em></span>
<input id="clip" type="range" min="0.1" max="2" step="0.1" value="1">
<span id="clipVal" class="val">1.0</span>
</label>
<label>
<span>Noise multiplier <em>σ</em></span>
<input id="noise" type="range" min="0.5" max="3" step="0.1" value="1.1">
<span id="noiseVal" class="val">1.1</span>
</label>
<label>
<span>Batch size</span>
<input id="batch" type="range" min="16" max="256" step="16" value="64">
<span id="batchVal" class="val">64</span>
</label>
<label>
<span>Dataset size</span>
<input id="ndata" type="range" min="500" max="5000" step="500" value="1000">
<span id="ndataVal" class="val">1000</span>
</label>
</div>
<div class="btns">
<button id="btnStep" type="button">Step one epoch</button>
<button id="btnRun" type="button">Run training</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div class="info-row">
<span class="badge" id="epsBadge">ϵ = 0.00</span>
<span class="badge sec" id="epochBadge">Epoch 0</span>
<span class="badge sec" id="deltaBadge">δ = 1e-5</span>
</div>
<canvas id="chart" width="480" height="200"></canvas>
<p id="verdict" class="verdict"></p>
/* Layout and colour for the DP-SGD privacy budget visualiser */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem 1rem; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .4rem; font-size: .85rem; }
label span:first-child { min-width: 7rem; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.val { min-width: 2.8rem; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.btns { display: flex; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.info-row { display: flex; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
.badge { font: 700 13px ui-monospace, monospace; padding: .25rem .55rem;
border-radius: 6px; background: #e63946; color: #fff; }
.badge.sec { background: #1d3557; }
canvas { width: 100%; border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.verdict { font-size: .9rem; font-weight: 600; margin: .45rem 0 0; min-height: 1.3em; }
.verdict.good { color: #0a7d33; }
.verdict.warn { color: #b45a00; }
.verdict.bad { color: #c92f3c; }
/* DP-SGD privacy budget simulator â Renyi DP moments accountant (simplified) */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* training history â one epsilon value per step */
let history = [];
let animId = null;
/* parameter controls */
const $ = id => document.getElementById(id);
const clip = $('clip');
const noise = $('noise');
const batch = $('batch');
const ndata = $('ndata');
function getParams() {
return {
C: parseFloat(clip.value),
sigma: parseFloat(noise.value),
q: parseInt(batch.value, 10) / parseInt(ndata.value, 10),
delta: 1e-5
};
}
/* Renyi DP per-step cost (simplified Gaussian mechanism with subsampling) */
function rdpStep(alpha, C, sigma, q) {
/* per-step RDP cost for Gaussian mechanism with subsampling rate q */
const sens = 1.0;
const base = alpha / (2 * sigma * sigma) * sens * sens;
return q * q * base;
}
/* convert accumulated RDP to (epsilon, delta)-DP */
function epsilonFromRdp(rdpEps, alpha, delta) {
/* RDP to approx (eps, delta)-DP via standard conversion */
return rdpEps + Math.log(1 / delta) / (alpha - 1);
}
function computeEpsilon(steps, C, sigma, q, delta) {
/* minimise over Renyi orders alpha to get the tightest epsilon */
const alphas = [2, 4, 8, 16, 32, 64];
let best = Infinity;
for (const alpha of alphas) {
const rdp = steps * rdpStep(alpha, C, sigma, q);
const eps = epsilonFromRdp(rdp, alpha, delta);
if (eps < best) best = eps;
}
return best;
}
const STEPS_PER_EPOCH = 50;
function currentEpoch() { return Math.floor(history.length / STEPS_PER_EPOCH); }
function stepOnce() {
const { C, sigma, q, delta } = getParams();
const step = history.length + 1;
const eps = computeEpsilon(step, C, sigma, q, delta);
history.push(eps);
draw();
updateBadges();
}
function draw() {
const canvas = $('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
/* draw chart axes with padding */
const pad = { t: 14, r: 12, b: 32, l: 42 };
const iW = W - pad.l - pad.r;
const iH = H - pad.t - pad.b;
const maxSteps = Math.max(history.length, STEPS_PER_EPOCH);
const maxEps = Math.max(...history, 1, 10);
/* horizontal grid lines with y-axis labels */
ctx.strokeStyle = '#e0e6ec';
ctx.lineWidth = 1;
for (let i = 0; i <= 4; i++) {
const y = pad.t + (iH * i / 4);
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + iW, y); ctx.stroke();
ctx.fillStyle = '#666'; ctx.font = '10px system-ui'; ctx.textAlign = 'right';
ctx.fillText((maxEps * (1 - i / 4)).toFixed(1), pad.l - 4, y + 3);
}
/* x-axis epoch labels */
ctx.fillStyle = '#666'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
for (let e = 0; e <= Math.ceil(maxSteps / STEPS_PER_EPOCH); e++) {
const x = pad.l + (e * STEPS_PER_EPOCH / maxSteps) * iW;
ctx.fillText(String(e), x, H - pad.b + 14);
}
ctx.fillStyle = '#444'; ctx.font = '11px system-ui';
ctx.fillText('Epoch', pad.l + iW / 2, H - 2);
/* rotated y-axis label */
ctx.save(); ctx.translate(11, pad.t + iH / 2); ctx.rotate(-Math.PI / 2);
ctx.textAlign = 'center'; ctx.fillText('Δ (privacy cost)', 0, 0); ctx.restore();
/* reference epsilon thresholds (strong / moderate / weak) */
const thresholds = [{ v: 1, label: 'Δ=1', col: '#0a7d33' }, { v: 8, label: 'Δ=8', col: '#b45a00' }, { v: 20, label: 'Δ=20', col: '#c92f3c' }];
for (const t of thresholds) {
if (t.v > maxEps * 1.05) continue;
const y = pad.t + iH * (1 - t.v / maxEps);
ctx.strokeStyle = t.col; ctx.setLineDash([4, 3]); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + iW, y); ctx.stroke();
ctx.fillStyle = t.col; ctx.font = '9px system-ui'; ctx.textAlign = 'left';
ctx.fillText(t.label, pad.l + 2, y - 2);
}
ctx.setLineDash([]);
/* draw the epsilon curve */
if (history.length > 0) {
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
ctx.beginPath();
history.forEach((eps, i) => {
const x = pad.l + ((i + 1) / maxSteps) * iW;
const y = pad.t + iH * (1 - Math.min(eps, maxEps) / maxEps);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
}
function updateBadges() {
const eps = history.length ? history[history.length - 1] : 0;
$('epsBadge').textContent = 'Δ = ' + eps.toFixed(2);
$('epochBadge').textContent = 'Epoch ' + currentEpoch();
const v = $('verdict');
if (history.length === 0) { v.textContent = ''; v.className = 'verdict'; return; }
if (eps <= 1) { v.textContent = i18nFmt('Δ = %eps% â strong privacy. Model leaks very little.', { eps: eps.toFixed(2) }); v.className = 'verdict good'; }
else if (eps <= 8) { v.textContent = i18nFmt('Δ = %eps% â moderate privacy. Acceptable for many real deployments.', { eps: eps.toFixed(2) }); v.className = 'verdict warn'; }
else { v.textContent = i18nFmt('Δ = %eps% â weak privacy. Budget nearly exhausted; consider more noise.', { eps: eps.toFixed(2) }); v.className = 'verdict bad'; }
}
function syncLabels() {
$('clipVal').textContent = parseFloat(clip.value).toFixed(1);
$('noiseVal').textContent = parseFloat(noise.value).toFixed(1);
$('batchVal').textContent = batch.value;
$('ndataVal').textContent = ndata.value;
}
function reset() {
if (animId) { clearInterval(animId); animId = null; }
history = [];
const canvas = $('chart');
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
draw();
updateBadges();
$('btnRun').textContent = 'Run training';
}
function runAuto() {
if (animId) {
clearInterval(animId); animId = null;
$('btnRun').textContent = 'Run training';
return;
}
$('btnRun').textContent = 'Pause';
animId = setInterval(() => {
stepOnce();
if (history.length >= STEPS_PER_EPOCH * 10) {
clearInterval(animId); animId = null;
$('btnRun').textContent = 'Run training';
}
}, 60);
}
$('btnStep').addEventListener('click', stepOnce);
$('btnRun').addEventListener('click', runAuto);
$('btnReset').addEventListener('click', reset);
[clip, noise, batch, ndata].forEach(el => {
el.addEventListener('input', () => { syncLabels(); reset(); });
});
syncLabels();
draw();
Comments
Loading comments...