// ======= Analytics & Identity Bootstrap =======
const ANALYTICS_ENDPOINT = '/api/track';
const COOKIE_NAME = 'vid';
// Generate a stable session id (per browser)
const sessionId = (() => {
const key = 'dpsgd_session_id';
let id = localStorage.getItem(key);
if (!id) { id = (crypto.randomUUID?.() || (String(Date.now()) + Math.random().toString(16).slice(2))); localStorage.setItem(key, id); }
return id;
})();
// Minimal user context (non-PII by default). Call identify({ id, role, org, plan }) if you have a login.
let userContext = { vid: null, id: null, role: null, org: null, plan: null };
async function initIdentity() {
try {
const r = await fetch('/api/whoami', { credentials: 'same-origin' });
const info = await r.json();
if (info && info.vid) userContext.vid = info.vid;
} catch {}
}
initIdentity();
function identify(user) {
userContext = { ...userContext, ...{
id: user?.id ?? null,
role: user?.role ?? null,
org: user?.org ?? null,
plan: user?.plan ?? null,
}};
track('identify', { user: { id: userContext.id, role: userContext.role, org: userContext.org, plan: userContext.plan } });
}
// Fire-and-forget tracker
function track(eventType, payload = {}) {
const body = {
t: Date.now(),
sessionId,
eventType,
path: location.pathname,
payload,
user: { id: userContext.id, role: userContext.role, org: userContext.org, plan: userContext.plan },
vid: userContext.vid
};
const data = new Blob([JSON.stringify(body)], { type: 'application/json' });
if (!(navigator.sendBeacon && navigator.sendBeacon(ANALYTICS_ENDPOINT, data))) {
fetch(ANALYTICS_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), credentials: 'same-origin' }).catch(()=>{});
}
}
// Global click listener (optional; captures generic UI clicks)
document.addEventListener('click', (e) => {
const target = e.target?.closest?.('[data-track], button, a, .nav-link, .tab');
if (!target) return;
const name = target.getAttribute('data-track') || target.id || target.textContent?.trim()?.slice(0, 60);
if (!name) return;
track('ui_click', { name });
}, { capture: true });
// ======= End Analytics Bootstrap =======
class DPSGDExplorer {
constructor() {
this.trainingChart = null;
this.privacyChart = null;
this.gradientChart = null;
this.isTraining = false;
this.currentView = 'epochs'; // 'epochs' or 'iterations'
this.epochsData = [];
this.iterationsData = [];
this.initializeUI();
}
initializeUI() {
// Initialize parameter controls
this.initializeSliders();
this.initializePresets();
this.initializeTabs();
this.initializeCharts();
// Add event listeners
document.getElementById('train-button')?.addEventListener('click', () => this.toggleTraining());
document.getElementById('train-button')?.addEventListener('click', () => { try { track('train_toggle', (this.getParameters?.()||{})); } catch (e) {} });
// Add view toggle listeners
document.getElementById('view-epochs')?.addEventListener('click', () => this.switchView('epochs'));
document.getElementById('view-iterations')?.addEventListener('click', () => this.switchView('iterations'));
}
initializeSliders() {
// Parameter sliders
const sliders = {
'clipping-norm': document.getElementById('clipping-norm'),
'noise-multiplier': document.getElementById('noise-multiplier'),
'batch-size': document.getElementById('batch-size'),
'learning-rate': document.getElementById('learning-rate'),
'epochs': document.getElementById('epochs')
};
// Add event listeners to sliders
for (const [id, slider] of Object.entries(sliders)) {
if (slider) {
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
document.getElementById(`${id}-value`).textContent = value.toFixed(1);
// Update privacy budget
this.updatePrivacyBudget();
try { track('param_change', { param: id, value }); } catch (e) {};
// Update gradient visualization when clipping norm changes
if (id === 'clipping-norm') {
this.updateGradientVisualization(value);
}
});
}
}
// Add event listener for the visual clipping norm slider
const visualSlider = document.getElementById('clipping-norm-visual');
if (visualSlider) {
visualSlider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
document.getElementById('clipping-norm-visual-value').textContent = value.toFixed(1);
this.updateGradientVisualization(value);
});
}
}
initializePresets() {
const presets = {
'high-privacy': {
clippingNorm: 1.0,
noiseMultiplier: 1.5,
batchSize: 256,
learningRate: 0.005,
epochs: 30
},
'balanced': {
clippingNorm: 1.0,
noiseMultiplier: 1.0,
batchSize: 128,
learningRate: 0.01,
epochs: 30
},
'high-utility': {
clippingNorm: 1.5,
noiseMultiplier: 0.5,
batchSize: 64,
learningRate: 0.02,
epochs: 30
}
};
// Add event listeners to preset buttons
for (const [preset, values] of Object.entries(presets)) {
document.getElementById(`preset-${preset}`)?.addEventListener('click', () => {
this.applyPreset(values);
});
}
}
initializeTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => { try { track('tab_click', { tab: tab.dataset?.tab || tab.id || 'unknown' }); } catch (e) {} });
tab.addEventListener('click', () => {
const tabsContainer = tab.closest('.tabs');
tabsContainer.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const tabName = tab.getAttribute('data-tab');
const panel = tab.closest('.panel');
panel.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
panel.querySelector(`#${tabName}-tab`)?.classList.add('active');
});
});
}
initializeCharts() {
const trainingCtx = document.getElementById('training-chart')?.getContext('2d');
const privacyCtx = document.getElementById('privacy-chart')?.getContext('2d');
const gradientCtx = document.getElementById('gradient-chart')?.getContext('2d');
if (trainingCtx) {
this.trainingChart = new Chart(trainingCtx, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Accuracy',
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
data: [],
yAxisID: 'y',
borderWidth: 3,
pointRadius: 4,
pointHoverRadius: 6,
tension: 0.1
},
{
label: 'Loss',
borderColor: '#f44336',
backgroundColor: 'rgba(244, 67, 54, 0.1)',
data: [],
yAxisID: 'y1',
borderWidth: 3,
pointRadius: 4,
pointHoverRadius: 6,
tension: 0.1,
borderDash: [5, 5] // Dashed line to differentiate from accuracy
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
padding: 20,
font: {
size: 12,
weight: 'bold'
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#fff',
bodyColor: '#fff',
borderColor: '#ddd',
borderWidth: 1
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Accuracy (%)',
color: '#4caf50',
font: {
size: 14,
weight: 'bold'
}
},
min: 0,
max: 100,
ticks: {
color: '#4caf50',
font: {
weight: 'bold'
},
callback: function(value) {
return value + '%';
}
},
grid: {
color: 'rgba(76, 175, 80, 0.2)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Loss',
color: '#f44336',
font: {
size: 14,
weight: 'bold'
}
},
min: 0,
max: 3, // More reasonable max for loss
ticks: {
color: '#f44336',
font: {
weight: 'bold'
},
callback: function(value) {
return value.toFixed(1);
}
},
grid: {
drawOnChartArea: false, // Don't overlay grid lines
color: 'rgba(244, 67, 54, 0.2)'
},
},
x: {
title: {
display: true,
text: 'Training Progress',
font: {
size: 12,
weight: 'bold'
}
},
ticks: {
font: {
size: 11
}
}
}
}
}
});
}
if (privacyCtx) {
this.privacyChart = new Chart(privacyCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Privacy Budget (ε)',
borderColor: '#3f51b5',
data: []
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Privacy Budget (ε)'
}
}
}
}
});
}
if (gradientCtx) {
this.gradientChart = new Chart(gradientCtx, {
type: 'scatter',
data: {
datasets: [
{
label: 'Before Clipping',
borderColor: '#2196f3',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
data: [],
showLine: true
},
{
label: 'After Clipping',
borderColor: '#f44336',
backgroundColor: 'rgba(244, 67, 54, 0.1)',
data: [],
showLine: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'linear',
position: 'bottom',
title: {
display: true,
text: 'Gradient Norm'
},
min: 0
},
y: {
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Density'
},
min: 0
}
},
plugins: {
annotation: {
annotations: {
line1: {
type: 'line',
xMin: 1,
xMax: 1,
borderColor: '#f44336',
borderWidth: 2,
borderDash: [5, 5],
label: {
content: 'Clipping Threshold',
display: true,
position: 'top'
}
}
}
}
}
}
});
}
}
async updatePrivacyBudget() {
const params = this.getParameters();
try {
const response = await fetch('/api/privacy-budget', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
});
const data = await response.json();
// Update UI
const budgetValue = document.getElementById('budget-value');
const budgetFill = document.getElementById('budget-fill');
if (budgetValue && budgetFill) {
budgetValue.textContent = data.epsilon.toFixed(2);
budgetFill.style.width = `${Math.min(data.epsilon / 10 * 100, 100)}%`;
try { track('privacy_budget_update', { epsilon: data.epsilon }); } catch (e) {};
// Update class for coloring
budgetFill.classList.remove('low', 'medium', 'high');
if (data.epsilon <= 1) {
budgetFill.classList.add('low');
} else if (data.epsilon <= 5) {
budgetFill.classList.add('medium');
} else {
budgetFill.classList.add('high');
}
}
} catch (error) {
console.error('Error calculating privacy budget:', error);
}
}
async toggleTraining() {
if (this.isTraining) {
this.stopTraining();
} else {
await this.startTraining();
}
}
async startTraining() {
const trainButton = document.getElementById('train-button');
const trainingStatus = document.getElementById('training-status');
if (!trainButton || this.isTraining) return;
this.isTraining = true;
trainButton.textContent = 'Stop Training';
trainButton.classList.add('running');
trainingStatus.style.display = 'flex';
// Reset charts
this.resetCharts();
try {
console.log('Starting training with parameters:', this.getParameters()); // Debug log
const response = await fetch('/api/train', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(this.getParameters())
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Unknown error occurred');
}
console.log('Received training data:', data); // Debug log
// Update charts and results
this.updateCharts(data);
this.updateResults(data);
} catch (error) {
console.error('Training error:', error);
// Show error message to user
const errorMessage = document.createElement('div');
errorMessage.className = 'error-message';
errorMessage.textContent = error.message || 'An error occurred during training';
document.querySelector('.lab-main').insertBefore(errorMessage, document.querySelector('.lab-main').firstChild);
// Remove error message after 5 seconds
setTimeout(() => {
errorMessage.remove();
}, 5000);
} finally {
this.stopTraining();
}
}
stopTraining() {
this.isTraining = false;
const trainButton = document.getElementById('train-button');
if (trainButton) {
trainButton.textContent = 'Run Training';
trainButton.classList.remove('running');
}
document.getElementById('training-status').style.display = 'none';
}
resetCharts() {
if (this.trainingChart) {
this.trainingChart.data.labels = [];
this.trainingChart.data.datasets[0].data = [];
this.trainingChart.data.datasets[1].data = [];
this.trainingChart.update();
}
if (this.privacyChart) {
this.privacyChart.data.labels = [];
this.privacyChart.data.datasets[0].data = [];
this.privacyChart.update();
}
if (this.gradientChart) {
this.gradientChart.data.datasets[0].data = [];
this.gradientChart.data.datasets[1].data = [];
this.gradientChart.update();
}
}
switchView(view) {
this.currentView = view;
try { track('view_switch', { view }); } catch (e) {};
// Update button states
document.querySelectorAll('.view-toggle').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(`view-${view}`).classList.add('active');
// Update chart with current data
if (view === 'epochs' && this.epochsData.length > 0) {
this.updateChartsWithData(this.epochsData, 'epochs');
} else if (view === 'iterations' && this.iterationsData.length > 0) {
this.updateChartsWithData(this.iterationsData, 'iterations');
}
}
updateCharts(data) {
if (!this.trainingChart || !data) return;
console.log('Updating charts with data:', data); // Debug log
// Store data for view switching
if (data.epochs_data) {
this.epochsData = data.epochs_data;
}
if (data.iterations_data) {
this.iterationsData = data.iterations_data;
}
// Use current view to determine which data to display
if (this.currentView === 'epochs' && this.epochsData.length > 0) {
this.updateChartsWithData(this.epochsData, 'epochs');
} else if (this.currentView === 'iterations' && this.iterationsData.length > 0) {
this.updateChartsWithData(this.iterationsData, 'iterations');
} else if (this.epochsData.length > 0) {
// Fallback to epochs if iterations not available
this.updateChartsWithData(this.epochsData, 'epochs');
}
}
updateChartsWithData(chartData, dataType) {
if (!this.trainingChart || !chartData) return;
// Update training metrics chart
const labels = chartData.map(d =>
dataType === 'epochs' ? `Epoch ${d.epoch}` : `Iter ${d.iteration}`
);
const accuracies = chartData.map(d => d.accuracy);
const losses = chartData.map(d => d.loss);
console.log(`${dataType} - Accuracies:`, accuracies);
console.log(`${dataType} - Losses:`, losses);
this.trainingChart.data.labels = labels;
this.trainingChart.data.datasets[0].data = accuracies;
this.trainingChart.data.datasets[1].data = losses;
// Auto-adjust loss scale based on actual data
const maxLoss = Math.max(...losses);
const minLoss = Math.min(...losses);
this.trainingChart.options.scales.y1.max = Math.max(maxLoss * 1.1, 3);
this.trainingChart.options.scales.y1.min = Math.max(0, minLoss * 0.9);
// Update chart info
const chartInfo = document.getElementById('chart-info');
if (chartInfo) {
chartInfo.textContent = `Showing ${chartData.length} data points (${dataType})`;
}
this.trainingChart.update();
// Update current epoch display
const currentEpoch = document.getElementById('current-epoch');
const totalEpochs = document.getElementById('total-epochs');
if (currentEpoch && totalEpochs && dataType === 'epochs') {
currentEpoch.textContent = chartData.length;
totalEpochs.textContent = this.getParameters().epochs;
}
// Update privacy budget chart (only for epochs view)
if (this.privacyChart && dataType === 'epochs') {
const privacyBudgets = chartData.map((_, i) =>
this.calculateEpochPrivacy(i + 1)
);
this.privacyChart.data.labels = labels;
this.privacyChart.data.datasets[0].data = privacyBudgets;
this.privacyChart.update();
}
// Update gradient visualization
if (this.gradientChart) {
const clippingNorm = this.getParameters().clipping_norm;
// Generate gradient data if not provided in chartData
let gradientData;
if (chartData[chartData.length - 1]?.gradient_info) {
gradientData = chartData[chartData.length - 1].gradient_info;
} else {
// Generate synthetic gradient data
const beforeClipping = [];
const afterClipping = [];
// Generate log-normal distributed gradients
const mu = Math.log(clippingNorm) - 0.5;
const sigma = 0.8;
for (let i = 0; i < 100; i++) {
const u1 = Math.random();
const u2 = Math.random();
const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
const norm = Math.exp(mu + sigma * z);
const density = Math.exp(-(Math.pow(Math.log(norm) - mu, 2) / (2 * sigma * sigma))) /
(norm * sigma * Math.sqrt(2 * Math.PI));
const y = 0.2 + 0.8 * (density / 0.8) + 0.1 * (Math.random() - 0.5);
beforeClipping.push({ x: norm, y: y });
afterClipping.push({ x: Math.min(norm, clippingNorm), y: y });
}
gradientData = {
before_clipping: beforeClipping.sort((a, b) => a.x - b.x),
after_clipping: afterClipping.sort((a, b) => a.x - b.x)
};
}
// Update gradient chart
this.gradientChart.data.datasets[0].data = gradientData.before_clipping;
this.gradientChart.data.datasets[1].data = gradientData.after_clipping;
// Update clipping threshold line
this.gradientChart.options.plugins.annotation.annotations.line1 = {
type: 'line',
xMin: clippingNorm,
xMax: clippingNorm,
borderColor: '#f44336',
borderWidth: 2,
borderDash: [5, 5],
label: {
content: `Clipping Threshold (C=${clippingNorm.toFixed(1)})`,
display: true,
position: 'top'
}
};
// Update x-axis scale based on clipping norm
this.gradientChart.options.scales.x.max = Math.max(clippingNorm * 2.5, 5);
this.gradientChart.update('active');
}
}
updateResults(data) {
// Hide no-results message and show results content
document.getElementById('no-results').style.display = 'none';
document.getElementById('results-content').style.display = 'block';
// Update metrics
document.getElementById('accuracy-value').textContent =
data.final_metrics.accuracy.toFixed(1) + '%';
document.getElementById('loss-value').textContent =
data.final_metrics.loss.toFixed(3);
document.getElementById('training-time-value').textContent =
data.final_metrics.training_time.toFixed(1) + 's';
// Update privacy budget display (make it dynamic)
const privacyBudgetElement = document.getElementById('privacy-budget-value');
if (privacyBudgetElement) {
privacyBudgetElement.textContent = `ε=${data.privacy_budget.toFixed(1)}`;
}
// Update privacy-utility trade-off explanation dynamically
const tradeoffElement = document.getElementById('tradeoff-explanation');
if (tradeoffElement) {
const accuracy = data.final_metrics.accuracy.toFixed(1);
const epsilon = data.privacy_budget.toFixed(1);
// Generate realistic trade-off assessment
let tradeoffAssessment;
if (data.final_metrics.accuracy >= 85) {
tradeoffAssessment = "This is an excellent trade-off for most applications.";
} else if (data.final_metrics.accuracy >= 75) {
tradeoffAssessment = "This is a good trade-off for most applications.";
} else if (data.final_metrics.accuracy >= 65) {
tradeoffAssessment = "This trade-off may be acceptable for privacy-critical applications.";
} else if (data.final_metrics.accuracy >= 50) {
tradeoffAssessment = "Low utility - consider reducing noise or increasing clipping norm.";
} else {
tradeoffAssessment = "Very poor utility - privacy parameters need significant adjustment.";
}
tradeoffElement.textContent =
`This model achieved ${accuracy}% accuracy with a privacy budget of ε=${epsilon}. ${tradeoffAssessment}`;
}
// Update recommendations
const recommendationList = document.querySelector('.recommendation-list');
recommendationList.innerHTML = '';
data.recommendations.forEach(rec => {
const item = document.createElement('li');
item.className = 'recommendation-item';
item.innerHTML = `
${rec.icon}
${rec.text}
`;
recommendationList.appendChild(item);
});
}
getParameters() {
return {
clipping_norm: parseFloat(document.getElementById('clipping-norm').value),
noise_multiplier: parseFloat(document.getElementById('noise-multiplier').value),
batch_size: parseInt(document.getElementById('batch-size').value),
learning_rate: parseFloat(document.getElementById('learning-rate').value),
epochs: parseInt(document.getElementById('epochs').value),
dataset: document.getElementById('dataset-select').value,
model_architecture: document.getElementById('model-select').value
};
}
applyPreset(values) {
document.getElementById('clipping-norm').value = values.clippingNorm;
document.getElementById('noise-multiplier').value = values.noiseMultiplier;
document.getElementById('batch-size').value = values.batchSize;
document.getElementById('learning-rate').value = values.learningRate;
document.getElementById('epochs').value = values.epochs;
// Update displayed values
document.getElementById('clipping-norm-value').textContent = values.clippingNorm;
document.getElementById('noise-multiplier-value').textContent = values.noiseMultiplier;
document.getElementById('batch-size-value').textContent = values.batchSize;
document.getElementById('learning-rate-value').textContent = values.learningRate;
document.getElementById('epochs-value').textContent = values.epochs;
this.updatePrivacyBudget();
}
calculateEpochPrivacy(epoch) {
const params = this.getParameters();
// Get dataset size based on selection
let datasetSize;
switch(params.dataset) {
case 'cifar10':
datasetSize = 50000; // CIFAR-10 training set size
break;
case 'fashion-mnist':
datasetSize = 60000; // Fashion-MNIST training set size
break;
case 'mnist':
default:
datasetSize = 60000; // MNIST training set size
break;
}
const samplingRate = params.batch_size / datasetSize;
const steps = epoch * (1 / samplingRate);
const delta = 1e-5;
const c = Math.sqrt(2 * Math.log(1.25 / delta));
return Math.min((c * samplingRate * Math.sqrt(steps)) / params.noise_multiplier, 10);
}
updateGradientVisualization(clippingNorm) {
if (!this.gradientChart) return;
// Generate random gradient norms following a log-normal distribution
const numPoints = 100;
const beforeClipping = [];
const afterClipping = [];
// Parameters for log-normal distribution
const mu = Math.log(clippingNorm) - 0.5;
const sigma = 0.8;
// Generate gradient norms
for (let i = 0; i < numPoints; i++) {
// Generate log-normal distributed gradient norms
const u1 = Math.random();
const u2 = Math.random();
const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
const norm = Math.exp(mu + sigma * z);
// Calculate density using kernel density estimation
const density = Math.exp(-(Math.pow(Math.log(norm) - mu, 2) / (2 * sigma * sigma))) / (norm * sigma * Math.sqrt(2 * Math.PI));
// Normalize density and add some randomness
const y = 0.2 + 0.8 * (density / 0.8) + 0.1 * (Math.random() - 0.5);
beforeClipping.push({ x: norm, y: y });
afterClipping.push({ x: Math.min(norm, clippingNorm), y: y });
}
// Sort points by x-value for smoother lines
beforeClipping.sort((a, b) => a.x - b.x);
afterClipping.sort((a, b) => a.x - b.x);
// Update chart data
this.gradientChart.data.datasets[0].data = beforeClipping;
this.gradientChart.data.datasets[1].data = afterClipping;
// Update clipping threshold line
this.gradientChart.options.plugins.annotation.annotations.line1 = {
type: 'line',
xMin: clippingNorm,
xMax: clippingNorm,
borderColor: '#f44336',
borderWidth: 2,
borderDash: [5, 5],
label: {
content: `Clipping Threshold (C=${clippingNorm.toFixed(1)})`,
display: true,
position: 'top'
}
};
// Update x-axis scale based on clipping norm
this.gradientChart.options.scales.x.max = Math.max(clippingNorm * 2.5, 5);
// Update the chart with animation
this.gradientChart.update('active');
}
updateGradientVisualizationWithData(beforeClipping, afterClipping, clippingNorm) {
if (!this.gradientChart) return;
// Update chart data with real training data
this.gradientChart.data.datasets[0].data = beforeClipping;
this.gradientChart.data.datasets[1].data = afterClipping;
// Update clipping threshold line
this.gradientChart.options.plugins.annotation.annotations.line1 = {
type: 'line',
xMin: clippingNorm,
xMax: clippingNorm,
borderColor: '#f44336',
borderWidth: 2,
borderDash: [5, 5],
label: {
content: `Clipping Threshold (C=${clippingNorm.toFixed(1)})`,
display: true,
position: 'top'
}
};
// Update x-axis scale based on clipping norm
this.gradientChart.options.scales.x.max = Math.max(clippingNorm * 2.5, 5);
// Update the chart with animation
this.gradientChart.update('active');
}
}
// Initialize the application when the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.dpsgdExplorer = new DPSGDExplorer();
});
function setOptimalParameters() {
// Set optimal parameters based on actual MNIST DP-SGD training results
// These values achieve ~95% accuracy with reasonable privacy budget (ε≈15)
document.getElementById('clipping-norm').value = '2.0'; // Balanced clipping norm
document.getElementById('noise-multiplier').value = '1.0'; // Moderate noise for good privacy
document.getElementById('batch-size').value = '256'; // Large batches for DP-SGD stability
document.getElementById('learning-rate').value = '0.05'; // Balanced learning rate
document.getElementById('epochs').value = '30'; // Sufficient epochs for convergence
// Update displays
updateClippingNormDisplay();
updateNoiseMultiplierDisplay();
updateBatchSizeDisplay();
updateLearningRateDisplay();
updateEpochsDisplay();
}