Shuya Feng commited on
Commit
adae711
·
1 Parent(s): 4f456aa

chore: Remove old HTML files

Browse files
app/__pycache__/__init__.cpython-38.pyc DELETED
Binary file (444 Bytes)
 
app/__pycache__/routes.cpython-38.pyc DELETED
Binary file (1.58 kB)
 
app/training/__pycache__/__init__.cpython-38.pyc DELETED
Binary file (266 Bytes)
 
app/training/__pycache__/mock_trainer.cpython-38.pyc DELETED
Binary file (4.21 kB)
 
app/training/__pycache__/privacy_calculator.cpython-38.pyc DELETED
Binary file (3.39 kB)
 
complete-dpsgd-explorer.html DELETED
@@ -1,1654 +0,0 @@
1
- <div id="recommendations-tab" class="tab-content">
2
- <h3 style="margin-top: 0; margin-bottom: 1rem; font-size: 1rem;">Recommendations</h3>
3
- <ul class="recommendation-list">
4
- <li class="recommendation-item">
5
- <span class="recommendation-icon">👍</span>
6
- <span>Current configuration seems well-balanced. Experiment with small parameter changes to optimize further.</span>
7
- </li>
8
- <li class="recommendation-item">
9
- <span class="recommendation-icon">🔍</span>
10
- <span>Try increasing the clipping norm slightly to see if it improves accuracy without significantly affecting privacy.</span>
11
- </li>
12
- <li class="recommendation-item">
13
- <span class="recommendation-icon">⚙️</span>
14
- <span>Consider increasing batch size to stabilize training with the current noise level.</span>
15
- </li>
16
- </ul>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
- </div>
22
- </div>
23
-
24
- <!-- Learning Hub Section (hidden initially) -->
25
- <div id="learning-hub-section" style="display: none;">
26
- <h1 class="section-title">Learning Hub</h1>
27
-
28
- <div class="learning-container">
29
- <div class="learning-sidebar">
30
- <h2 class="panel-title">DP-SGD Concepts</h2>
31
- <ul class="learning-steps">
32
- <li class="learning-step active" data-step="intro">Introduction to Differential Privacy</li>
33
- <li class="learning-step" data-step="dp-concepts">Core DP Concepts</li>
34
- <li class="learning-step" data-step="sgd-basics">SGD Refresher</li>
35
- <li class="learning-step" data-step="dpsgd-intro">DP-SGD: Core Modifications</li>
36
- <li class="learning-step" data-step="parameters">Hyperparameter Deep Dive</li>
37
- <li class="learning-step" data-step="privacy-accounting">Privacy Accounting</li>
38
- </ul>
39
- </div>
40
-
41
- <div class="learning-content">
42
- <div id="intro-content" class="step-content active">
43
- <h2>Introduction to Differential Privacy</h2>
44
- <p>Differential Privacy (DP) is a mathematical framework that provides strong privacy guarantees when performing analyses on sensitive data. It ensures that the presence or absence of any single individual's data has a minimal effect on the output of an analysis.</p>
45
-
46
- <h3>Why is Differential Privacy Important?</h3>
47
- <p>Traditional anonymization techniques often fail to protect privacy. With enough auxiliary information, it's possible to re-identify individuals in supposedly "anonymized" datasets. Differential privacy addresses this by adding carefully calibrated noise to the analysis process.</p>
48
-
49
- <div class="concept-highlight">
50
- <h4>Key Insight</h4>
51
- <p>Differential privacy creates plausible deniability. By adding controlled noise, it becomes mathematically impossible to confidently determine whether any individual's data was used in the analysis.</p>
52
- </div>
53
-
54
- <h3>The Privacy-Utility Trade-off</h3>
55
- <p>There's an inherent trade-off between privacy and utility (accuracy) in DP. More privacy means more noise, which typically reduces accuracy. The challenge is finding the right balance for your specific application.</p>
56
-
57
- <div class="concept-box">
58
- <div class="box1">
59
- <h4>Strong Privacy (Low ε)</h4>
60
- <ul>
61
- <li>More noise added</li>
62
- <li>Lower accuracy</li>
63
- <li>Better protection for sensitive data</li>
64
- </ul>
65
- </div>
66
- <div class="box2">
67
- <h4>Strong Utility (Higher ε)</h4>
68
- <ul>
69
- <li>Less noise added</li>
70
- <li>Higher accuracy</li>
71
- <li>Reduced privacy guarantees</li>
72
- </ul>
73
- </div>
74
- </div>
75
- </div>
76
-
77
- <div id="dp-concepts-content" class="step-content">
78
- <h2>Core Differential Privacy Concepts</h2>
79
-
80
- <h3>The Formal Definition</h3>
81
- <p>A mechanism M is (ε,δ)-differentially private if for all neighboring datasets D and D' (differing in one record), and for all possible outputs S:</p>
82
- <div class="formula">
83
- P(M(D) ∈ S) ≤ e^ε × P(M(D') ∈ S) + δ
84
- </div>
85
-
86
- <h3>Key Parameters</h3>
87
- <p><strong>ε (epsilon)</strong>: The privacy budget. Lower values mean stronger privacy but typically lower utility.</p>
88
- <p><strong>δ (delta)</strong>: The probability of the privacy guarantee being broken. Usually set very small (e.g., 10^-5).</p>
89
-
90
- <h3>Differential Privacy Mechanisms</h3>
91
- <p><strong>Laplace Mechanism</strong>: Adds noise from a Laplace distribution to numeric queries.</p>
92
- <p><strong>Gaussian Mechanism</strong>: Adds noise from a Gaussian (normal) distribution. This is used in DP-SGD.</p>
93
- <p><strong>Exponential Mechanism</strong>: Used for non-numeric outputs, selects an output based on a probability distribution.</p>
94
-
95
- <h3>Privacy Accounting</h3>
96
- <p>When you apply multiple differentially private operations, the privacy loss (ε) accumulates. This is known as composition.</p>
97
- <p>Advanced composition theorems and privacy accountants help track the total privacy spend.</p>
98
- </div>
99
-
100
- <div id="sgd-basics-content" class="step-content">
101
- <h2>Stochastic Gradient Descent Refresher</h2>
102
-
103
- <h3>Standard SGD</h3>
104
- <p>Stochastic Gradient Descent (SGD) is an optimization algorithm used to train machine learning models by iteratively updating parameters based on gradients computed from mini-batches of data.</p>
105
-
106
- <h3>The Basic Update Rule</h3>
107
- <p>The standard SGD update for a batch B is:</p>
108
- <div class="formula">
109
- θ ← θ - η∇L(θ; B)
110
- </div>
111
- <p>Where:</p>
112
- <ul>
113
- <li>θ represents the model parameters</li>
114
- <li>η is the learning rate</li>
115
- <li>∇L(θ; B) is the average gradient of the loss over the batch B</li>
116
- </ul>
117
-
118
- <h3>Privacy Concerns with Standard SGD</h3>
119
- <p>Standard SGD can leak information about individual training examples through the gradients. For example:</p>
120
- <ul>
121
- <li>Gradients might be larger for outliers or unusual examples</li>
122
- <li>Model memorization of sensitive data can be extracted through attacks</li>
123
- <li>Gradient values can be used in reconstruction attacks</li>
124
- </ul>
125
-
126
- <p>These privacy concerns motivate the need for differentially private training methods.</p>
127
- </div>
128
-
129
- <div id="dpsgd-intro-content" class="step-content">
130
- <h2>DP-SGD: Core Modifications</h2>
131
-
132
- <h3>How DP-SGD Differs from Standard SGD</h3>
133
- <p>Differentially Private SGD modifies standard SGD in two key ways:</p>
134
-
135
- <div class="concept-box">
136
- <div class="box1">
137
- <h4>1. Per-Sample Gradient Clipping</h4>
138
- <p>Compute gradients for each example individually, then clip their L2 norm to a threshold C.</p>
139
- <p>This limits the influence of any single training example on the model update.</p>
140
- </div>
141
-
142
- <div class="box2">
143
- <h4>2. Noise Addition</h4>
144
- <p>Add Gaussian noise to the sum of clipped gradients before applying the update.</p>
145
- <p>The noise scale is proportional to the clipping threshold and the noise multiplier.</p>
146
- </div>
147
- </div>
148
-
149
- <h3>The DP-SGD Update Rule</h3>
150
- <p>The DP-SGD update can be summarized as:</p>
151
- <ol>
152
- <li>Compute per-sample gradients: g<sub>i</sub> = ∇L(θ; x<sub>i</sub>)</li>
153
- <li>Clip each gradient: g̃<sub>i</sub> = g<sub>i</sub> × min(1, C/||g<sub>i</sub>||<sub>2</sub>)</li>
154
- <li>Add noise: ḡ = (1/|B|) × (∑g̃<sub>i</sub> + N(0, σ²C²I))</li>
155
- <li>Update parameters: θ ← θ - η × ḡ</li>
156
- </ol>
157
-
158
- <p>Where:</p>
159
- <ul>
160
- <li>C is the clipping norm</li>
161
- <li>σ is the noise multiplier</li>
162
- <li>B is the batch</li>
163
- </ul>
164
- </div>
165
-
166
- <div id="parameters-content" class="step-content">
167
- <h2>Hyperparameter Deep Dive</h2>
168
-
169
- <p>DP-SGD introduces several new hyperparameters that need to be tuned carefully:</p>
170
-
171
- <h3>Clipping Norm (C)</h3>
172
- <p>The maximum allowed L2 norm for any individual gradient.</p>
173
- <ul>
174
- <li><strong>Too small:</strong> Gradients are over-clipped, limiting learning</li>
175
- <li><strong>Too large:</strong> Requires more noise to achieve the same privacy guarantee</li>
176
- <li><strong>Typical range:</strong> 0.1 to 10.0, depending on the dataset and model</li>
177
- </ul>
178
-
179
- <h3>Noise Multiplier (σ)</h3>
180
- <p>Controls the amount of noise added to the gradients.</p>
181
- <ul>
182
- <li><strong>Higher σ:</strong> Better privacy, worse utility</li>
183
- <li><strong>Lower σ:</strong> Better utility, worse privacy</li>
184
- <li><strong>Typical range:</strong> 0.5 to 2.0 for most practical applications</li>
185
- </ul>
186
-
187
- <h3>Batch Size</h3>
188
- <p>Affects both training dynamics and privacy accounting.</p>
189
- <ul>
190
- <li><strong>Larger batches:</strong> Reduce variance from noise, but change sampling probability</li>
191
- <li><strong>Smaller batches:</strong> More update steps, potentially consuming more privacy budget</li>
192
- <li><strong>Typical range:</strong> 64 to 1024, larger than standard SGD</li>
193
- </ul>
194
-
195
- <h3>Learning Rate (η)</h3>
196
- <p>May need adjustment compared to non-private training.</p>
197
- <ul>
198
- <li><strong>DP-SGD often requires:</strong> Lower learning rates or careful scheduling</li>
199
- <li><strong>Reason:</strong> Added noise can destabilize training with high learning rates</li>
200
- </ul>
201
-
202
- <h3>Number of Epochs</h3>
203
- <p>More epochs consume more privacy budget.</p>
204
- <ul>
205
- <li><strong>Trade-off:</strong> More training vs. privacy budget consumption</li>
206
- <li><strong>Early stopping:</strong> Often beneficial for balancing accuracy and privacy</li>
207
- </ul>
208
- </div>
209
-
210
- <div id="privacy-accounting-content" class="step-content">
211
- <h2>Privacy Accounting</h2>
212
-
213
- <h3>Tracking Privacy Budget</h3>
214
- <p>Privacy accounting is the process of keeping track of the total privacy loss (ε) throughout training.</p>
215
-
216
- <h3>Common Methods</h3>
217
- <div style="display: flex; flex-direction: column; gap: 15px; margin: 15px 0;">
218
- <div class="concept-highlight">
219
- <h4>Moment Accountant</h4>
220
- <p>Used in the original DP-SGD paper, provides tight bounds on the privacy loss.</p>
221
- <p>Tracks the moments of the privacy loss random variable.</p>
222
- </div>
223
-
224
- <div class="concept-highlight">
225
- <h4>Rényi Differential Privacy (RDP)</h4>
226
- <p>Alternative accounting method based on Rényi divergence.</p>
227
- <p>Often used in modern implementations like TensorFlow Privacy and Opacus.</p>
228
- </div>
229
-
230
- <div class="concept-highlight">
231
- <h4>Analytical Gaussian Mechanism</h4>
232
- <p>Simpler method for specific mechanisms like the Gaussian Mechanism.</p>
233
- <p>Less tight bounds but easier to compute.</p>
234
- </div>
235
- </div>
236
-
237
- <h3>Privacy Budget Allocation</h3>
238
- <p>With a fixed privacy budget (ε), you must decide how to allocate it:</p>
239
- <ul>
240
- <li><strong>Fixed noise, variable epochs:</strong> Set noise level, train until budget is exhausted</li>
241
- <li><strong>Fixed epochs, variable noise:</strong> Set desired epochs, calculate required noise</li>
242
- <li><strong>Advanced techniques:</strong> Privacy filters, odometers, and adaptive mechanisms</li>
243
- </ul>
244
-
245
- <h3>Practical Implementation</h3>
246
- <p>In practice, privacy accounting is handled by libraries like:</p>
247
- <ul>
248
- <li>TensorFlow Privacy</li>
249
- <li>PyTorch Opacus</li>
250
- <li>Diffprivlib (IBM)</li>
251
- </ul>
252
- </div>
253
- </div>
254
- </div>
255
- </div>
256
- </main>
257
-
258
- <footer class="footer">
259
- <p>DP-SGD Explorer - An Educational Tool for Differential Privacy in Machine Learning</p>
260
- <p>© 2023 - For educational purposes</p>
261
- </footer>
262
- </div>
263
-
264
- <script>
265
- // Load Chart.js library dynamically
266
- (function loadScript() {
267
- const script = document.createElement('script');
268
- script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
269
- script.onload = function() {
270
- // Initialize the app after Chart.js loads
271
- initializeApp();
272
- };
273
- script.onerror = function() {
274
- alert('Failed to load Chart.js. Please check your internet connection.');
275
- };
276
- document.head.appendChild(script);
277
- })();
278
-
279
- function initializeApp() {
280
- // Navigation
281
- const navLearning = document.getElementById('nav-learning');
282
- const navPlayground = document.getElementById('nav-playground');
283
- const learningSection = document.getElementById('learning-hub-section');
284
- const playgroundSection = document.getElementById('playground-section');
285
-
286
- navLearning.addEventListener('click', function() {
287
- navLearning.classList.add('active');
288
- navPlayground.classList.remove('active');
289
- learningSection.style.display = 'block';
290
- playgroundSection.style.display = 'none';
291
- });
292
-
293
- navPlayground.addEventListener('click', function() {
294
- navPlayground.classList.add('active');
295
- navLearning.classList.remove('active');
296
- playgroundSection.style.display = 'block';
297
- learningSection.style.display = 'none';
298
- });
299
-
300
- // Tabs in Playground
301
- const tabs = document.querySelectorAll('.tab');
302
- tabs.forEach(tab => {
303
- tab.addEventListener('click', function() {
304
- // Get parent tabs container
305
- const tabsContainer = this.closest('.tabs');
306
- // Remove active class from all sibling tabs
307
- tabsContainer.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
308
- // Add active class to clicked tab
309
- this.classList.add('active');
310
-
311
- // Handle tab content
312
- const tabName = this.getAttribute('data-tab');
313
- const panel = this.closest('.panel');
314
- panel.querySelectorAll('.tab-content').forEach(content => {
315
- content.classList.remove('active');
316
- });
317
-
318
- // Find the tab content in the panel
319
- panel.querySelector(`#${tabName}-tab`)?.classList.add('active');
320
- });
321
- });
322
-
323
- // Learning Hub Steps
324
- const learningSteps = document.querySelectorAll('.learning-step');
325
- learningSteps.forEach(step => {
326
- step.addEventListener('click', function() {
327
- // Remove active class from all steps
328
- learningSteps.forEach(s => s.classList.remove('active'));
329
- // Add active class to clicked step
330
- this.classList.add('active');
331
-
332
- // Handle step content
333
- const stepName = this.getAttribute('data-step');
334
- document.querySelectorAll('.step-content').forEach(content => {
335
- content.classList.remove('active');
336
- });
337
- document.getElementById(`${stepName}-content`).classList.add('active');
338
- });
339
- });
340
-
341
- // Parameter sliders
342
- const clipNormSlider = document.getElementById('clipping-norm');
343
- const clipNormValue = document.getElementById('clipping-norm-value');
344
- const noiseMultiplierSlider = document.getElementById('noise-multiplier');
345
- const noiseMultiplierValue = document.getElementById('noise-multiplier-value');
346
- const batchSizeSlider = document.getElementById('batch-size');
347
- const batchSizeValue = document.getElementById('batch-size-value');
348
- const learningRateSlider = document.getElementById('learning-rate');
349
- const learningRateValue = document.getElementById('learning-rate-value');
350
- const epochsSlider = document.getElementById('epochs');
351
- const epochsValue = document.getElementById('epochs-value');
352
-
353
- // Update displayed values
354
- clipNormSlider.addEventListener('input', function() {
355
- clipNormValue.textContent = this.value;
356
- updatePrivacyBudget();
357
-
358
- // Update gradient clipping visualization if visible
359
- const gradientTab = document.getElementById('gradients-tab');
360
- if (gradientTab.classList.contains('active')) {
361
- const gradCanvas = document.getElementById('gradient-canvas');
362
- const gradCtx = gradCanvas.getContext('2d');
363
- drawGradientVisualization(gradCtx, parseFloat(this.value));
364
- }
365
- });
366
-
367
- noiseMultiplierSlider.addEventListener('input', function() {
368
- noiseMultiplierValue.textContent = this.value;
369
- updatePrivacyBudget();
370
- });
371
-
372
- batchSizeSlider.addEventListener('input', function() {
373
- batchSizeValue.textContent = this.value;
374
- updatePrivacyBudget();
375
- });
376
-
377
- learningRateSlider.addEventListener('input', function() {
378
- learningRateValue.textContent = parseFloat(this.value).toFixed(3);
379
- });
380
-
381
- epochsSlider.addEventListener('input', function() {
382
- epochsValue.textContent = this.value;
383
- updatePrivacyBudget();
384
- });
385
-
386
- // Privacy budget calculation (simplified educational version)
387
- function updatePrivacyBudget() {
388
- const clipNorm = parseFloat(clipNormSlider.value);
389
- const noiseMultiplier = parseFloat(noiseMultiplierSlider.value);
390
- const batchSize = parseInt(batchSizeSlider.value);
391
- const epochs = parseInt(epochsSlider.value);
392
-
393
- // Sample rate (percentage of dataset seen in each batch)
394
- const sampleRate = batchSize / 60000; // Assuming MNIST size
395
-
396
- // Number of steps
397
- const steps = epochs * (1 / sampleRate);
398
-
399
- // Simple formula for analytical Gaussian (simplified for educational purposes)
400
- const delta = 1e-5; // Common delta value
401
- const c = Math.sqrt(2 * Math.log(1.25 / delta));
402
- let epsilon = (c * sampleRate * Math.sqrt(steps)) / noiseMultiplier;
403
- epsilon = Math.min(epsilon, 10); // Cap at 10 for UI purposes
404
-
405
- // Update UI
406
- const budgetValue = document.getElementById('budget-value');
407
- const budgetFill = document.getElementById('budget-fill');
408
-
409
- budgetValue.textContent = epsilon.toFixed(2);
410
- budgetFill.style.width = `${Math.min(epsilon / 10 * 100, 100)}%`;
411
-
412
- // Update class for coloring
413
- budgetFill.classList.remove('low', 'medium', 'high');
414
- if (epsilon <= 1) {
415
- budgetFill.classList.add('low');
416
- } else if (epsilon <= 5) {
417
- budgetFill.classList.add('medium');
418
- } else {
419
- budgetFill.classList.add('high');
420
- }
421
- }
422
-
423
- // Initialize charts
424
- function initializeCharts() {
425
- // Training chart (dummy data to start)
426
- const trainingCtx = document.getElementById('training-chart').getContext('2d');
427
- window.trainingChart = new Chart(trainingCtx, {
428
- type: 'line',
429
- data: {
430
- labels: [],
431
- datasets: [
432
- {
433
- label: 'Accuracy',
434
- borderColor: '#4caf50',
435
- data: [],
436
- yAxisID: 'y'
437
- },
438
- {
439
- label: 'Loss',
440
- borderColor: '#f44336',
441
- data: [],
442
- yAxisID: 'y1'
443
- }
444
- ]
445
- },
446
- options: {
447
- responsive: true,
448
- interaction: {
449
- mode: 'index',
450
- intersect: false,
451
- },
452
- scales: {
453
- y: {
454
- type: 'linear',
455
- display: true,
456
- position: 'left',
457
- title: {
458
- display: true,
459
- text: 'Accuracy (%)'
460
- }
461
- },
462
- y1: {
463
- type: 'linear',
464
- display: true,
465
- position: 'right',
466
- title: {
467
- display: true,
468
- text: 'Loss'
469
- },
470
- grid: {
471
- drawOnChartArea: false,
472
- },
473
- }
474
- }
475
- }
476
- });
477
-
478
- // Privacy budget chart (dummy data to start)
479
- const privacyCtx = document.getElementById('privacy-chart').getContext('2d');
480
- window.privacyChart = new Chart(privacyCtx, {
481
- type: 'line',
482
- data: {
483
- labels: [],
484
- datasets: [{
485
- label: 'Privacy Budget (ε)',
486
- borderColor: '#3f51b5',
487
- data: []
488
- }]
489
- },
490
- options: {
491
- responsive: true,
492
- scales: {
493
- y: {
494
- title: {
495
- display: true,
496
- text: 'Privacy Budget (ε)'
497
- }
498
- },
499
- x: {
500
- title: {
501
- display: true,
502
- text: 'Epoch'
503
- }
504
- }
505
- }
506
- }
507
- });
508
-
509
- // Draw gradient clipping visualization
510
- const gradCanvas = document.getElementById('gradient-canvas');
511
- const gradCtx = gradCanvas.getContext('2d');
512
- drawGradientVisualization(gradCtx, parseFloat(clipNormSlider.value));
513
- }
514
-
515
- // Draw gradient clipping visualization
516
- function drawGradientVisualization(ctx, clipNorm) {
517
- const width = ctx.canvas.width;
518
- const height = ctx.canvas.height;
519
- const padding = { top: 20, right: 30, bottom: 40, left: 60 };
520
-
521
- // Clear canvas
522
- ctx.clearRect(0, 0, width, height);
523
-
524
- // Draw axes
525
- ctx.strokeStyle = '#ccc';
526
- ctx.lineWidth = 1;
527
-
528
- // Y-axis
529
- ctx.beginPath();
530
- ctx.moveTo(padding.left, padding.top);
531
- ctx.lineTo(padding.left, height - padding.bottom);
532
- ctx.stroke();
533
-
534
- // X-axis
535
- ctx.beginPath();
536
- ctx.moveTo(padding.left, height - padding.bottom);
537
- ctx.lineTo(width - padding.right, height - padding.bottom);
538
- ctx.stroke();
539
-
540
- // Draw distribution curve - before clipping
541
- ctx.beginPath();
542
- ctx.moveTo(padding.left, height - padding.bottom);
543
-
544
- // Lognormal-like curve
545
- const chartWidth = width - padding.left - padding.right;
546
- const chartHeight = height - padding.top - padding.bottom;
547
-
548
- for (let i = 0; i < chartWidth; i++) {
549
- const x = padding.left + i;
550
- const xValue = (i / chartWidth) * 10; // Scale to 0-10 range
551
-
552
- // Lognormal-ish function
553
- let y = Math.exp(-Math.pow(Math.log(xValue + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5);
554
- y = height - padding.bottom - y * chartHeight * 0.8;
555
-
556
- if (i === 0) {
557
- ctx.moveTo(x, y);
558
- } else {
559
- ctx.lineTo(x, y);
560
- }
561
- }
562
-
563
- ctx.strokeStyle = '#ff9800';
564
- ctx.lineWidth = 3;
565
- ctx.stroke();
566
-
567
- // Calculate clipping threshold position
568
- const clipX = padding.left + (clipNorm / 10) * chartWidth;
569
-
570
- // Draw clipping threshold line
571
- ctx.beginPath();
572
- ctx.moveTo(clipX, padding.top);
573
- ctx.lineTo(clipX, height - padding.bottom);
574
- ctx.strokeStyle = '#f44336';
575
- ctx.lineWidth = 2;
576
- ctx.setLineDash([5, 3]);
577
- ctx.stroke();
578
- ctx.setLineDash([]);
579
-
580
- // Add labels
581
- ctx.fillStyle = '#333';
582
- ctx.font = '12px Arial';
583
- ctx.textAlign = 'center';
584
- ctx.fillText('Gradient L2 Norm', width / 2, height - 5);
585
- ctx.textAlign = 'left';
586
- ctx.fillText(`Clipping Threshold (C = ${clipNorm})`, clipX + 5, padding.top + 15);
587
-
588
- // Draw clipped curve
589
- ctx.beginPath();
590
- ctx.moveTo(padding.left, height - padding.bottom);
591
-
592
- // Draw up to clipping threshold
593
- for (let i = 0; i < chartWidth; i++) {
594
- const x = padding.left + i;
595
- const xValue = (i / chartWidth) * 10;
596
-
597
- if (x > clipX) break;
598
-
599
- // Same curve as before
600
- let y = Math.exp(-Math.pow(Math.log(xValue + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5);
601
- y = height - padding.bottom - y * chartHeight * 0.8;
602
-
603
- if (i === 0) {
604
- ctx.moveTo(x, y);
605
- } else {
606
- ctx.lineTo(x, y);
607
- }
608
- }
609
-
610
- // Calculate y at clipping point
611
- const clipY = height - padding.bottom - Math.exp(-Math.pow(Math.log(clipNorm + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5) * chartHeight * 0.8;
612
-
613
- // Draw horizontal line at clipping threshold
614
- ctx.lineTo(clipX, clipY);
615
- ctx.lineTo(width - padding.right, clipY);
616
-
617
- ctx.strokeStyle = '#4caf50';
618
- ctx.lineWidth = 3;
619
- ctx.stroke();
620
-
621
- // Legend
622
- ctx.fillStyle = '#ff9800';
623
- ctx.fillRect(padding.left, padding.top, 10, 10);
624
- ctx.fillStyle = '#333';
625
- ctx.textAlign = 'left';
626
- ctx.fillText('Original Gradients', padding.left + 15, padding.top + 10);
627
-
628
- ctx.fillStyle = '#4caf50';
629
- ctx.fillRect(padding.left, padding.top + 20, 10, 10);
630
- ctx.fillStyle = '#333';
631
- ctx.fillText('Clipped Gradients', padding.left + 15, padding.top + 30);
632
- }
633
-
634
- // Train button functionality
635
- const trainButton = document.getElementById('train-button');
636
- trainButton.addEventListener('click', function() {
637
- if (this.textContent.trim() === 'Run Training') {
638
- startTraining();
639
- } else {
640
- stopTraining();
641
- }
642
- });
643
-
644
- // Presets
645
- document.getElementById('preset-high-privacy').addEventListener('click', function() {
646
- clipNormSlider.value = 1.0;
647
- clipNormValue.textContent = 1.0;
648
- noiseMultiplierSlider.value = 1.5;
649
- noiseMultiplierValue.textContent = 1.5;
650
- batchSizeSlider.value = 256;
651
- batchSizeValue.textContent = 256;
652
- learningRateSlider.value = 0.005;
653
- learningRateValue.textContent = 0.005;
654
- epochsSlider.value = 10;
655
- epochsValue.textContent = 10;
656
- updatePrivacyBudget();
657
- });
658
-
659
- document.getElementById('preset-balanced').addEventListener('click', function() {
660
- clipNormSlider.value = 1.0;
661
- clipNormValue.textContent = 1.0;
662
- noiseMultiplierSlider.value = 1.0;
663
- noiseMultiplierValue.textContent = 1.0;
664
- batchSizeSlider.value = 128;
665
- batchSizeValue.textContent = 128;
666
- learningRateSlider.value = 0.01;
667
- learningRateValue.textContent = 0.01;
668
- epochsSlider.value = 8;
669
- epochsValue.textContent = 8;
670
- updatePrivacyBudget();
671
- });
672
-
673
- document.getElementById('preset-high-utility').addEventListener('click', function() {
674
- clipNormSlider.value = 1.5;
675
- clipNormValue.textContent = 1.5;
676
- noiseMultiplierSlider.value = 0.5;
677
- noiseMultiplierValue.textContent = 0.5;
678
- batchSizeSlider.value = 64;
679
- batchSizeValue.textContent = 64;
680
- learningRateSlider.value = 0.02;
681
- learningRateValue.textContent = 0.02;
682
- epochsSlider.value = 5;
683
- epochsValue.textContent = 5;
684
- updatePrivacyBudget();
685
- });
686
-
687
- // Simulated training
688
- let trainingInterval;
689
- let currentEpoch = 0;
690
- const totalEpochs = () => parseInt(epochsSlider.value);
691
-
692
- function startTraining() {
693
- // Update UI
694
- trainButton.textContent = 'Stop Training';
695
- trainButton.classList.add('running');
696
- document.getElementById('training-status').style.display = 'flex';
697
- document.getElementById('total-epochs').textContent = totalEpochs();
698
-
699
- // Reset training data
700
- currentEpoch = 0;
701
- const epochs = Array.from({length: totalEpochs()}, (_, i) => i + 1);
702
-
703
- // Reset charts
704
- window.trainingChart.data.labels = epochs;
705
- window.trainingChart.data.datasets[0].data = [];
706
- window.trainingChart.data.datasets[1].data = [];
707
- window.trainingChart.update();
708
-
709
- window.privacyChart.data.labels = epochs;
710
- window.privacyChart.data.datasets[0].data = [];
711
- window.privacyChart.update();
712
-
713
- // Start training simulation
714
- trainingInterval = setInterval(simulateEpoch, 1000);
715
- }
716
-
717
- function stopTraining() {
718
- clearInterval(trainingInterval);
719
- trainButton.textContent = 'Run Training';
720
- trainButton.classList.remove('running');
721
- document.getElementById('training-status').style.display = 'none';
722
- }
723
-
724
- function simulateEpoch() {
725
- if (currentEpoch >= totalEpochs()) {
726
- // Training complete
727
- stopTraining();
728
- showResults();
729
- return;
730
- }
731
-
732
- // Update epoch counter
733
- currentEpoch++;
734
- document.getElementById('current-epoch').textContent = currentEpoch;
735
-
736
- // Get parameters
737
- const noiseMultiplier = parseFloat(noiseMultiplierSlider.value);
738
- const clipNorm = parseFloat(clipNormSlider.value);
739
-
740
- // Calculate simulated metrics
741
- const baseAccuracy = 0.75;
742
- const noisePenalty = noiseMultiplier * 0.05;
743
- const clipPenalty = Math.abs(1.0 - clipNorm) * 0.03;
744
- const epochBonus = Math.min(currentEpoch * 0.05, 0.15);
745
-
746
- // Calculate accuracy with some randomness
747
- const accuracy = Math.min(
748
- 0.98,
749
- baseAccuracy - noisePenalty - clipPenalty + epochBonus + (Math.random() * 0.02)
750
- );
751
-
752
- // Calculate loss
753
- const loss = Math.max(0.1, 1.0 - accuracy + (Math.random() * 0.05));
754
-
755
- // Calculate privacy budget
756
- const batchSize = parseInt(batchSizeSlider.value);
757
- const samplingRate = batchSize / 60000;
758
- const steps = currentEpoch * (1 / samplingRate);
759
- const delta = 1e-5;
760
- const c = Math.sqrt(2 * Math.log(1.25 / delta));
761
- const epsilon = (c * samplingRate * Math.sqrt(steps)) / noiseMultiplier;
762
-
763
- // Update charts
764
- window.trainingChart.data.datasets[0].data.push(accuracy * 100);
765
- window.trainingChart.data.datasets[1].data.push(loss);
766
- window.trainingChart.update();
767
-
768
- window.privacyChart.data.datasets[0].data.push(epsilon);
769
- window.privacyChart.update();
770
-
771
- // If this is the last epoch, show results
772
- if (currentEpoch >= totalEpochs()) {
773
- stopTraining();
774
- showResults();
775
- }
776
- }
777
-
778
- function showResults() {
779
- // Hide no-results message and show results content
780
- document.getElementById('no-results').style.display = 'none';
781
- document.getElementById('results-content').style.display = 'block';
782
-
783
- // Get final values from the charts
784
- const accuracy = window.trainingChart.data.datasets[0].data[window.trainingChart.data.datasets[0].data.length - 1];
785
- const loss = window.trainingChart.data.datasets[1].data[window.trainingChart.data.datasets[1].data.length - 1];
786
- const privacyBudget = window.privacyChart.data.datasets[0].data[window.privacyChart.data.datasets[0].data.length - 1];
787
-
788
- // Update results display
789
- document.getElementById('accuracy-value').textContent = accuracy.toFixed(1) + '%';
790
- document.getElementById('loss-value').textContent = loss.toFixed(3);
791
- document.getElementById('privacy-budget-value').textContent = 'ε = ' + privacyBudget.toFixed(2);
792
- document.getElementById('training-time-value').textContent = (totalEpochs() * 0.8).toFixed(1) + 's';
793
-
794
- // Update trade-off explanation
795
- const tradeoffExplanation = document.getElementById('tradeoff-explanation');
796
- let explanationText = `This model achieved ${accuracy.toFixed(1)}% accuracy with a privacy budget of ε=${privacyBudget.toFixed(2)}.`;
797
-
798
- if (accuracy > 90 && privacyBudget < 3) {
799
- explanationText += " This is an excellent balance of privacy and utility.";
800
- } else if (accuracy > 85 && privacyBudget < 5) {
801
- explanationText += " This is a good trade-off for most applications.";
802
- } else if (accuracy > 80) {
803
- explanationText += " Consider if this level of privacy is sufficient for your use case.";
804
- } else {
805
- explanationText += " You may want to adjust parameters to improve accuracy while maintaining privacy.";
806
- }
807
-
808
- tradeoffExplanation.textContent = explanationText;
809
-
810
- // Generate recommendations
811
- const recommendationList = document.querySelector('.recommendation-list');
812
- recommendationList.innerHTML = '';
813
-
814
- // Based on current parameters
815
- if (accuracy < 70) {
816
- addRecommendation('⚠️', 'Accuracy is low. Consider decreasing noise multiplier or increasing the number of epochs.');
817
- }
818
-
819
- if (privacyBudget > 8) {
820
- addRecommendation('🔓', 'Privacy budget is high. Consider increasing noise multiplier or reducing epochs for stronger privacy.');
821
- } else if (privacyBudget < 1 && accuracy < 85) {
822
- addRecommendation('🔒', 'Very strong privacy may be limiting accuracy. Consider a slight increase in privacy budget.');
823
- }
824
-
825
- if (parseFloat(clipNormSlider.value) < 0.5) {
826
- addRecommendation('✂️', 'Clipping norm is very low. This might be over-clipping gradients and limiting learning.');
827
- }
828
-
829
- if (parseInt(batchSizeSlider.value) < 32) {
830
- addRecommendation('📊', 'Small batch size can make training with DP-SGD unstable. Consider increasing batch size.');
831
- }
832
-
833
- // Add generic recommendation if none were added
834
- if (recommendationList.children.length === 0) {
835
- addRecommendation('👍', 'Current configuration seems well-balanced. Experiment with small parameter changes to optimize further.');
836
- }
837
- }
838
-
839
- function addRecommendation(icon, text) {
840
- const recommendationList = document.querySelector('.recommendation-list');
841
- const item = document.createElement('li');
842
- item.className = 'recommendation-item';
843
- item.innerHTML = `<span class="recommendation-icon">${icon}</span><span>${text}</span>`;
844
- recommendationList.appendChild(item);
845
- }
846
-
847
- // Initialize on page load
848
- updatePrivacyBudget();
849
- initializeCharts();
850
- }
851
- </script>
852
- </body>
853
- </html>
854
- <!DOCTYPE html>
855
- <html lang="en">
856
- <head>
857
- <meta charset="UTF-8">
858
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
859
- <title>DP-SGD Explorer</title>
860
- <style>
861
- :root {
862
- --primary-color: #3f51b5;
863
- --primary-light: #757de8;
864
- --primary-dark: #002984;
865
- --secondary-color: #4caf50;
866
- --accent-color: #ff9800;
867
- --error-color: #f44336;
868
- --text-primary: #333;
869
- --text-secondary: #666;
870
- --background-light: #fff;
871
- --background-off: #f5f7fa;
872
- --border-color: #ddd;
873
-
874
- --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
875
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
876
- }
877
-
878
- body {
879
- font-family: var(--font-family);
880
- margin: 0;
881
- padding: 0;
882
- background: var(--background-off);
883
- color: var(--text-primary);
884
- }
885
-
886
- .app-container {
887
- min-height: 100vh;
888
- display: flex;
889
- flex-direction: column;
890
- }
891
-
892
- .main-header {
893
- background-color: var(--primary-color);
894
- color: white;
895
- padding: 1rem;
896
- box-shadow: var(--shadow-sm);
897
- }
898
-
899
- .header-container {
900
- display: flex;
901
- justify-content: space-between;
902
- align-items: center;
903
- max-width: 1200px;
904
- margin: 0 auto;
905
- }
906
-
907
- .logo {
908
- font-size: 1.5rem;
909
- font-weight: bold;
910
- }
911
-
912
- .tagline {
913
- font-size: 0.8rem;
914
- opacity: 0.8;
915
- }
916
-
917
- .main-content {
918
- flex: 1;
919
- max-width: 1200px;
920
- margin: 0 auto;
921
- padding: 1rem;
922
- }
923
-
924
- .nav-list {
925
- list-style: none;
926
- display: flex;
927
- gap: 1rem;
928
- padding: 0;
929
- margin: 0;
930
- }
931
-
932
- .nav-link {
933
- color: white;
934
- cursor: pointer;
935
- padding: 0.5rem 1rem;
936
- border-radius: 4px;
937
- }
938
-
939
- .nav-link.active {
940
- background-color: rgba(255, 255, 255, 0.2);
941
- }
942
-
943
- .section-title {
944
- font-size: 2rem;
945
- color: var(--primary-dark);
946
- margin-bottom: 1.5rem;
947
- }
948
-
949
- .lab-container {
950
- display: grid;
951
- grid-template-columns: 300px 1fr;
952
- gap: 1.5rem;
953
- }
954
-
955
- @media (max-width: 900px) {
956
- .lab-container {
957
- grid-template-columns: 1fr;
958
- }
959
- }
960
-
961
- .panel {
962
- background: white;
963
- border-radius: 8px;
964
- padding: 1rem;
965
- box-shadow: var(--shadow-sm);
966
- }
967
-
968
- .panel-title {
969
- font-size: 1.2rem;
970
- margin-bottom: 1rem;
971
- color: var(--primary-dark);
972
- }
973
-
974
- .parameter-control {
975
- margin-bottom: 1rem;
976
- }
977
-
978
- .parameter-label {
979
- display: block;
980
- margin-bottom: 0.5rem;
981
- font-weight: 500;
982
- }
983
-
984
- .parameter-slider {
985
- width: 100%;
986
- margin-bottom: 0.5rem;
987
- }
988
-
989
- .slider-display {
990
- display: flex;
991
- justify-content: space-between;
992
- }
993
-
994
- .budget-display {
995
- margin-top: 1.5rem;
996
- padding: 1rem;
997
- background: var(--background-off);
998
- border-radius: 4px;
999
- }
1000
-
1001
- .budget-bar {
1002
- height: 8px;
1003
- background-color: #e0e0e0;
1004
- border-radius: 4px;
1005
- position: relative;
1006
- margin: 0.5rem 0;
1007
- }
1008
-
1009
- .budget-fill {
1010
- height: 100%;
1011
- border-radius: 4px;
1012
- background-color: var(--accent-color);
1013
- transition: width 0.3s ease;
1014
- }
1015
-
1016
- .budget-fill.low {
1017
- background-color: var(--secondary-color);
1018
- }
1019
-
1020
- .budget-fill.medium {
1021
- background-color: var(--accent-color);
1022
- }
1023
-
1024
- .budget-fill.high {
1025
- background-color: var(--error-color);
1026
- }
1027
-
1028
- .budget-scale {
1029
- display: flex;
1030
- justify-content: space-between;
1031
- font-size: 0.8rem;
1032
- color: var(--text-secondary);
1033
- }
1034
-
1035
- .control-button {
1036
- width: 100%;
1037
- padding: 0.8rem;
1038
- border: none;
1039
- border-radius: 4px;
1040
- background-color: var(--primary-color);
1041
- color: white;
1042
- font-weight: bold;
1043
- cursor: pointer;
1044
- margin-top: 1rem;
1045
- }
1046
-
1047
- .control-button:hover {
1048
- background-color: var(--primary-dark);
1049
- }
1050
-
1051
- .control-button.running {
1052
- background-color: var(--error-color);
1053
- }
1054
-
1055
- .control-button:disabled {
1056
- opacity: 0.6;
1057
- cursor: not-allowed;
1058
- }
1059
-
1060
- .chart-container {
1061
- height: 300px;
1062
- margin-bottom: 1rem;
1063
- position: relative;
1064
- }
1065
-
1066
- .chart {
1067
- width: 100%;
1068
- height: 100%;
1069
- border: 1px solid var(--border-color);
1070
- border-radius: 4px;
1071
- }
1072
-
1073
- .metrics-grid {
1074
- display: grid;
1075
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1076
- gap: 1rem;
1077
- margin-bottom: 1rem;
1078
- }
1079
-
1080
- .metric-card {
1081
- background-color: var(--background-off);
1082
- border-radius: 4px;
1083
- padding: 1rem;
1084
- text-align: center;
1085
- }
1086
-
1087
- .metric-value {
1088
- font-size: 1.5rem;
1089
- font-weight: bold;
1090
- margin-bottom: 0.5rem;
1091
- }
1092
-
1093
- .metric-label {
1094
- color: var(--text-secondary);
1095
- font-weight: 500;
1096
- }
1097
-
1098
- .recommendation-list {
1099
- list-style: none;
1100
- padding: 0;
1101
- margin: 0;
1102
- }
1103
-
1104
- .recommendation-item {
1105
- display: flex;
1106
- align-items: flex-start;
1107
- padding: 0.8rem 0;
1108
- border-bottom: 1px solid var(--border-color);
1109
- }
1110
-
1111
- .recommendation-icon {
1112
- margin-right: 0.5rem;
1113
- font-size: 1.2rem;
1114
- }
1115
-
1116
- .footer {
1117
- text-align: center;
1118
- padding: 1rem;
1119
- background-color: var(--primary-dark);
1120
- color: white;
1121
- margin-top: 2rem;
1122
- }
1123
-
1124
- .tooltip {
1125
- position: relative;
1126
- display: inline-block;
1127
- margin-left: 0.5rem;
1128
- }
1129
-
1130
- .tooltip-icon {
1131
- width: 16px;
1132
- height: 16px;
1133
- border-radius: 50%;
1134
- background-color: var(--primary-light);
1135
- color: white;
1136
- font-size: 12px;
1137
- display: flex;
1138
- align-items: center;
1139
- justify-content: center;
1140
- cursor: help;
1141
- }
1142
-
1143
- .tooltip-text {
1144
- visibility: hidden;
1145
- width: 200px;
1146
- background-color: #333;
1147
- color: white;
1148
- text-align: center;
1149
- border-radius: 4px;
1150
- padding: 0.5rem;
1151
- position: absolute;
1152
- z-index: 1;
1153
- bottom: 125%;
1154
- left: 50%;
1155
- margin-left: -100px;
1156
- opacity: 0;
1157
- transition: opacity 0.3s;
1158
- font-size: 0.8rem;
1159
- }
1160
-
1161
- .tooltip:hover .tooltip-text {
1162
- visibility: visible;
1163
- opacity: 1;
1164
- }
1165
-
1166
- .tabs {
1167
- display: flex;
1168
- margin-bottom: 1rem;
1169
- }
1170
-
1171
- .tab {
1172
- padding: 0.5rem 1rem;
1173
- cursor: pointer;
1174
- border-bottom: 2px solid transparent;
1175
- }
1176
-
1177
- .tab.active {
1178
- border-bottom: 2px solid var(--primary-color);
1179
- color: var(--primary-color);
1180
- }
1181
-
1182
- .tab-content {
1183
- display: none;
1184
- }
1185
-
1186
- .tab-content.active {
1187
- display: block;
1188
- }
1189
-
1190
- .canvas-container {
1191
- width: 100%;
1192
- height: 300px;
1193
- background: var(--background-off);
1194
- border-radius: 4px;
1195
- display: flex;
1196
- justify-content: center;
1197
- align-items: center;
1198
- }
1199
-
1200
- canvas {
1201
- max-width: 100%;
1202
- }
1203
-
1204
- .status-badge {
1205
- display: flex;
1206
- align-items: center;
1207
- margin-top: 1rem;
1208
- padding: 0.5rem;
1209
- background-color: var(--background-off);
1210
- border-radius: 4px;
1211
- }
1212
-
1213
- .pulse {
1214
- display: inline-block;
1215
- width: 10px;
1216
- height: 10px;
1217
- border-radius: 50%;
1218
- background: var(--secondary-color);
1219
- margin-right: 0.5rem;
1220
- animation: pulse 1.5s infinite;
1221
- }
1222
-
1223
- @keyframes pulse {
1224
- 0% {
1225
- box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);
1226
- }
1227
- 70% {
1228
- box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
1229
- }
1230
- 100% {
1231
- box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
1232
- }
1233
- }
1234
-
1235
- /* Learning Hub styles */
1236
- .learning-container {
1237
- display: grid;
1238
- grid-template-columns: 250px 1fr;
1239
- gap: 1.5rem;
1240
- }
1241
-
1242
- .learning-sidebar {
1243
- background: white;
1244
- border-radius: 8px;
1245
- padding: 1rem;
1246
- box-shadow: var(--shadow-sm);
1247
- }
1248
-
1249
- .learning-content {
1250
- background: white;
1251
- border-radius: 8px;
1252
- padding: 1.5rem;
1253
- box-shadow: var(--shadow-sm);
1254
- }
1255
-
1256
- .learning-steps {
1257
- list-style: none;
1258
- padding: 0;
1259
- margin: 0;
1260
- }
1261
-
1262
- .learning-step {
1263
- padding: 0.75rem 0.5rem;
1264
- border-radius: 4px;
1265
- cursor: pointer;
1266
- margin-bottom: 0.5rem;
1267
- }
1268
-
1269
- .learning-step.active {
1270
- background-color: var(--background-off);
1271
- color: var(--primary-color);
1272
- font-weight: 500;
1273
- }
1274
-
1275
- .step-content {
1276
- display: none;
1277
- }
1278
-
1279
- .step-content.active {
1280
- display: block;
1281
- }
1282
-
1283
- .concept-highlight {
1284
- background-color: var(--background-off);
1285
- border-radius: 4px;
1286
- padding: 1rem;
1287
- margin: 1rem 0;
1288
- }
1289
-
1290
- .formula {
1291
- background-color: #f5f7fa;
1292
- padding: 0.75rem;
1293
- border-radius: 4px;
1294
- font-family: monospace;
1295
- margin: 1rem 0;
1296
- }
1297
-
1298
- .concept-box {
1299
- display: flex;
1300
- margin: 1rem 0;
1301
- gap: 1rem;
1302
- }
1303
-
1304
- .concept-box > div {
1305
- flex: 1;
1306
- padding: 1rem;
1307
- border-radius: 8px;
1308
- }
1309
-
1310
- .concept-box .box1 {
1311
- background-color: #e3f2fd;
1312
- }
1313
-
1314
- .concept-box .box2 {
1315
- background-color: #fff8e1;
1316
- }
1317
- </style>
1318
- </head>
1319
- <body>
1320
- <div class="app-container">
1321
- <header class="main-header">
1322
- <div class="header-container">
1323
- <div>
1324
- <div class="logo">DP-SGD Explorer</div>
1325
- <div class="tagline">Interactive Learning & Experimentation</div>
1326
- </div>
1327
- <nav>
1328
- <ul class="nav-list">
1329
- <li><div class="nav-link" id="nav-learning">Learning Hub</div></li>
1330
- <li><div class="nav-link active" id="nav-playground">Playground</div></li>
1331
- </ul>
1332
- </nav>
1333
- </div>
1334
- </header>
1335
-
1336
- <main class="main-content">
1337
- <div id="playground-section">
1338
- <h1 class="section-title">DP-SGD Interactive Playground</h1>
1339
-
1340
- <div class="lab-container">
1341
- <!-- Sidebar - Configuration Panels -->
1342
- <div class="lab-sidebar">
1343
- <!-- Model Configuration Panel -->
1344
- <div class="panel">
1345
- <h2 class="panel-title">Model Configuration</h2>
1346
-
1347
- <div class="parameter-control">
1348
- <label for="dataset-select" class="parameter-label">
1349
- Dataset
1350
- <span class="tooltip">
1351
- <span class="tooltip-icon">?</span>
1352
- <span class="tooltip-text">The dataset used for training affects privacy budget calculations and model accuracy.</span>
1353
- </span>
1354
- </label>
1355
- <select id="dataset-select" class="parameter-select">
1356
- <option value="mnist">MNIST Digits</option>
1357
- <option value="fashion-mnist">Fashion MNIST</option>
1358
- <option value="cifar10">CIFAR-10</option>
1359
- </select>
1360
- </div>
1361
-
1362
- <div class="parameter-control">
1363
- <label for="model-select" class="parameter-label">
1364
- Model Architecture
1365
- <span class="tooltip">
1366
- <span class="tooltip-icon">?</span>
1367
- <span class="tooltip-text">The model architecture affects training time, capacity to learn, and resilience to noise.</span>
1368
- </span>
1369
- </label>
1370
- <select id="model-select" class="parameter-select">
1371
- <option value="simple-mlp">Simple MLP</option>
1372
- <option value="simple-cnn">Simple CNN</option>
1373
- <option value="advanced-cnn">Advanced CNN</option>
1374
- </select>
1375
- </div>
1376
-
1377
- <div style="margin-top: 1.5rem;">
1378
- <h3 style="margin-bottom: 0.5rem; font-size: 1rem;">Quick Presets</h3>
1379
- <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem;">
1380
- <button id="preset-high-privacy" style="padding: 0.5rem; text-align: center; background-color: #e3f2fd; border: none; border-radius: 4px; cursor: pointer;">
1381
- <div style="font-size: 1.2rem; margin-bottom: 0.2rem;">🔒</div>
1382
- <div style="font-weight: 500; margin-bottom: 0.2rem;">High Privacy</div>
1383
- <div style="font-size: 0.8rem; color: #666;">ε ≈ 1.2</div>
1384
- </button>
1385
- <button id="preset-balanced" style="padding: 0.5rem; text-align: center; background-color: #f1f8e9; border: none; border-radius: 4px; cursor: pointer;">
1386
- <div style="font-size: 1.2rem; margin-bottom: 0.2rem;">⚖️</div>
1387
- <div style="font-weight: 500; margin-bottom: 0.2rem;">Balanced</div>
1388
- <div style="font-size: 0.8rem; color: #666;">ε ≈ 3.0</div>
1389
- </button>
1390
- <button id="preset-high-utility" style="padding: 0.5rem; text-align: center; background-color: #fff8e1; border: none; border-radius: 4px; cursor: pointer;">
1391
- <div style="font-size: 1.2rem; margin-bottom: 0.2rem;">📈</div>
1392
- <div style="font-weight: 500; margin-bottom: 0.2rem;">High Utility</div>
1393
- <div style="font-size: 0.8rem; color: #666;">ε ≈ 8.0</div>
1394
- </button>
1395
- </div>
1396
- </div>
1397
- </div>
1398
-
1399
- <!-- DP-SGD Parameters Panel -->
1400
- <div class="panel" style="margin-top: 1rem;">
1401
- <h2 class="panel-title">DP-SGD Parameters</h2>
1402
-
1403
- <div class="parameter-control">
1404
- <label for="clipping-norm" class="parameter-label">
1405
- Clipping Norm (C)
1406
- <span class="tooltip">
1407
- <span class="tooltip-icon">?</span>
1408
- <span class="tooltip-text">Limits how much any single training example can affect the model update. Smaller values provide stronger privacy but can slow learning.</span>
1409
- </span>
1410
- </label>
1411
- <input type="range" id="clipping-norm" class="parameter-slider" min="0.1" max="5.0" step="0.1" value="1.0">
1412
- <div class="slider-display">
1413
- <span>0.1</span>
1414
- <span id="clipping-norm-value">1.0</span>
1415
- <span>5.0</span>
1416
- </div>
1417
- </div>
1418
-
1419
- <div class="parameter-control">
1420
- <label for="noise-multiplier" class="parameter-label">
1421
- Noise Multiplier (σ)
1422
- <span class="tooltip">
1423
- <span class="tooltip-icon">?</span>
1424
- <span class="tooltip-text">Controls how much noise is added to protect privacy. Higher values increase privacy but may reduce accuracy.</span>
1425
- </span>
1426
- </label>
1427
- <input type="range" id="noise-multiplier" class="parameter-slider" min="0.1" max="5.0" step="0.1" value="1.0">
1428
- <div class="slider-display">
1429
- <span>0.1</span>
1430
- <span id="noise-multiplier-value">1.0</span>
1431
- <span>5.0</span>
1432
- </div>
1433
- </div>
1434
-
1435
- <div class="parameter-control">
1436
- <label for="batch-size" class="parameter-label">
1437
- Batch Size
1438
- <span class="tooltip">
1439
- <span class="tooltip-icon">?</span>
1440
- <span class="tooltip-text">Number of examples processed in each training step. Affects both privacy accounting and training stability.</span>
1441
- </span>
1442
- </label>
1443
- <input type="range" id="batch-size" class="parameter-slider" min="16" max="512" step="16" value="64">
1444
- <div class="slider-display">
1445
- <span>16</span>
1446
- <span id="batch-size-value">64</span>
1447
- <span>512</span>
1448
- </div>
1449
- </div>
1450
-
1451
- <div class="parameter-control">
1452
- <label for="learning-rate" class="parameter-label">
1453
- Learning Rate (η)
1454
- <span class="tooltip">
1455
- <span class="tooltip-icon">?</span>
1456
- <span class="tooltip-text">Controls how quickly model parameters update. For DP-SGD, often needs to be smaller than standard SGD.</span>
1457
- </span>
1458
- </label>
1459
- <input type="range" id="learning-rate" class="parameter-slider" min="0.001" max="0.1" step="0.001" value="0.01">
1460
- <div class="slider-display">
1461
- <span>0.001</span>
1462
- <span id="learning-rate-value">0.01</span>
1463
- <span>0.1</span>
1464
- </div>
1465
- </div>
1466
-
1467
- <div class="parameter-control">
1468
- <label for="epochs" class="parameter-label">
1469
- Epochs
1470
- <span class="tooltip">
1471
- <span class="tooltip-icon">?</span>
1472
- <span class="tooltip-text">Number of complete passes through the dataset. More epochs improves learning but increases privacy budget consumption.</span>
1473
- </span>
1474
- </label>
1475
- <input type="range" id="epochs" class="parameter-slider" min="1" max="20" step="1" value="5">
1476
- <div class="slider-display">
1477
- <span>1</span>
1478
- <span id="epochs-value">5</span>
1479
- <span>20</span>
1480
- </div>
1481
- </div>
1482
-
1483
- <div class="budget-display">
1484
- <h3 style="margin-top: 0; margin-bottom: 0.5rem; font-size: 1rem;">
1485
- Estimated Privacy Budget (ε)
1486
- <span class="tooltip">
1487
- <span class="tooltip-icon">?</span>
1488
- <span class="tooltip-text">This is the estimated privacy loss from training with these parameters. Lower ε means stronger privacy guarantees.</span>
1489
- </span>
1490
- </h3>
1491
- <div style="display: flex; align-items: center; gap: 1rem;">
1492
- <div id="budget-value" style="font-size: 1.5rem; font-weight: bold; min-width: 60px;">2.47</div>
1493
- <div style="flex: 1;">
1494
- <div class="budget-bar">
1495
- <div id="budget-fill" class="budget-fill medium" style="width: 25%;"></div>
1496
- </div>
1497
- <div class="budget-scale">
1498
- <span>Stronger Privacy</span>
1499
- <span>Weaker Privacy</span>
1500
- </div>
1501
- </div>
1502
- </div>
1503
- </div>
1504
-
1505
- <button id="train-button" class="control-button">
1506
- Run Training
1507
- </button>
1508
- </div>
1509
- </div>
1510
-
1511
- <!-- Main Content - Visualizations and Results -->
1512
- <div class="lab-main">
1513
- <!-- Training Visualizer -->
1514
- <div class="panel">
1515
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
1516
- <h2 class="panel-title">Training Progress</h2>
1517
- <div class="tabs">
1518
- <div class="tab active" data-tab="training">Training Metrics</div>
1519
- <div class="tab" data-tab="gradients">Gradient Clipping</div>
1520
- <div class="tab" data-tab="privacy">Privacy Budget</div>
1521
- </div>
1522
- </div>
1523
-
1524
- <div id="training-tab" class="tab-content active">
1525
- <div class="chart-container">
1526
- <canvas id="training-chart" class="chart"></canvas>
1527
- </div>
1528
-
1529
- <div id="training-status" class="status-badge" style="display: none;">
1530
- <span class="pulse"></span>
1531
- <span style="font-weight: 500; color: #4caf50;">Training in progress</span>
1532
- <span style="margin-left: auto; font-weight: 500;">Epoch: <span id="current-epoch">1</span> / <span id="total-epochs">5</span></span>
1533
- </div>
1534
- </div>
1535
-
1536
- <div id="gradients-tab" class="tab-content">
1537
- <div style="margin-bottom: 1rem;">
1538
- <h3 style="font-size: 1rem; margin-bottom: 0.5rem;">Gradient Clipping Visualization</h3>
1539
- <p style="font-size: 0.9rem; color: var(--text-secondary);">
1540
- The chart below shows a distribution of gradient norms before and after clipping.
1541
- The vertical red line indicates the clipping threshold.
1542
- <span class="tooltip">
1543
- <span class="tooltip-icon">?</span>
1544
- <span class="tooltip-text">Clipping ensures no single example has too much influence on model updates, which is essential for differential privacy.</span>
1545
- </span>
1546
- </p>
1547
- </div>
1548
-
1549
- <div class="canvas-container">
1550
- <canvas id="gradient-canvas" width="600" height="300"></canvas>
1551
- </div>
1552
- </div>
1553
-
1554
- <div id="privacy-tab" class="tab-content">
1555
- <div style="margin-bottom: 1rem;">
1556
- <h3 style="font-size: 1rem; margin-bottom: 0.5rem;">Privacy Budget Consumption</h3>
1557
- <p style="font-size: 0.9rem; color: var(--text-secondary);">
1558
- This chart shows how the privacy budget (ε) accumulates during training.
1559
- <span class="tooltip">
1560
- <span class="tooltip-icon">?</span>
1561
- <span class="tooltip-text">In differential privacy, we track the 'privacy budget' (ε) which represents the amount of privacy loss. Lower values mean stronger privacy guarantees.</span>
1562
- </span>
1563
- </p>
1564
- </div>
1565
-
1566
- <div class="chart-container">
1567
- <canvas id="privacy-chart" class="chart"></canvas>
1568
- </div>
1569
- </div>
1570
- </div>
1571
-
1572
- <!-- Results Panel -->
1573
- <div class="panel" style="margin-top: 1rem;">
1574
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
1575
- <h2 class="panel-title">Results</h2>
1576
- <div class="tabs">
1577
- <div class="tab active" data-tab="metrics">Final Metrics</div>
1578
- <div class="tab" data-tab="recommendations">Recommendations</div>
1579
- </div>
1580
- </div>
1581
-
1582
- <!-- Initial no-results state -->
1583
- <div id="no-results" style="text-align: center; padding: 2rem 0;">
1584
- <p style="color: var(--text-secondary); margin-bottom: 1rem;">Run training to see results here</p>
1585
- <div style="font-size: 3rem; opacity: 0.5;">📊</div>
1586
- </div>
1587
-
1588
- <!-- Results content (hidden initially) -->
1589
- <div id="results-content" style="display: none;">
1590
- <div id="metrics-tab" class="tab-content active">
1591
- <div class="metrics-grid">
1592
- <div class="metric-card">
1593
- <div id="accuracy-value" class="metric-value" style="color: var(--primary-color);">92.4%</div>
1594
- <div class="metric-label">
1595
- Accuracy
1596
- <span class="tooltip">
1597
- <span class="tooltip-icon">?</span>
1598
- <span class="tooltip-text">Model performance on test data. Higher values are better.</span>
1599
- </span>
1600
- </div>
1601
- </div>
1602
-
1603
- <div class="metric-card">
1604
- <div id="loss-value" class="metric-value">0.283</div>
1605
- <div class="metric-label">
1606
- Loss
1607
- <span class="tooltip">
1608
- <span class="tooltip-icon">?</span>
1609
- <span class="tooltip-text">Final training loss. Lower values generally indicate better model fit.</span>
1610
- </span>
1611
- </div>
1612
- </div>
1613
-
1614
- <div class="metric-card">
1615
- <div id="privacy-budget-value" class="metric-value" style="color: var(--accent-color);">ε = 2.1</div>
1616
- <div class="metric-label">
1617
- Privacy Budget
1618
- <span class="tooltip">
1619
- <span class="tooltip-icon">?</span>
1620
- <span class="tooltip-text">Final privacy loss (ε). Lower values mean stronger privacy guarantees.</span>
1621
- </span>
1622
- </div>
1623
- </div>
1624
-
1625
- <div class="metric-card">
1626
- <div id="training-time-value" class="metric-value">3.7s</div>
1627
- <div class="metric-label">
1628
- Training Time
1629
- <span class="tooltip">
1630
- <span class="tooltip-icon">?</span>
1631
- <span class="tooltip-text">Total time spent on training, including privacy mechanisms.</span>
1632
- </span>
1633
- </div>
1634
- </div>
1635
- </div>
1636
-
1637
- <div style="background-color: var(--background-off); border-radius: 4px; padding: 1rem; margin-top: 1rem;">
1638
- <h3 style="margin-top: 0; margin-bottom: 0.5rem; font-size: 1rem;">Privacy-Utility Trade-off</h3>
1639
- <div style="position: relative; height: 8px; background-color: #e0e0e0; border-radius: 4px; margin: 1.5rem 0;">
1640
- <div style="position: absolute; top: -20px; left: 92%; transform: translateX(-50%);">
1641
- <span style="font-weight: 500; font-size: 0.8rem; color: var(--secondary-color);">Utility</span>
1642
- </div>
1643
- <div style="position: absolute; top: -20px; right: 79%; transform: translateX(50%);">
1644
- <span style="font-weight: 500; font-size: 0.8rem; color: var(--primary-color);">Privacy</span>
1645
- </div>
1646
- </div>
1647
- <p id="tradeoff-explanation" style="font-size: 0.9rem; color: var(--text-secondary); margin-top: 1rem;">
1648
- This model achieved 92.4% accuracy with a privacy budget of ε=2.1. This is a good trade-off for most applications.
1649
- </p>
1650
- </div>
1651
- </div>
1652
-
1653
- <div id="recommendations-tab" class="tab-content">
1654
- <h3 style
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
standalone-dpsgd-explorer.html DELETED
@@ -1,2546 +0,0 @@
1
- // Generate explanation about privacy-utility tradeoff
2
- const getTradeoffExplanation = (accuracy, privacyBudget) => {
3
- if (accuracy > 0.9 && privacyBudget < 3) {
4
- return " This is an excellent balance of privacy and utility.";
5
- } else if (accuracy > 0.85 && privacyBudget < 5) {
6
- return " This is a good trade-off for most applications.";
7
- } else if (accuracy > 0.8) {
8
- return " Consider if this level of privacy is sufficient for your use case.";
9
- } else {
10
- return " You may want to adjust parameters to improve accuracy while maintaining privacy.";
11
- }
12
- };
13
-
14
- // Generate recommendations based on training results
15
- const generateRecommendations = (results, config) => {
16
- const recommendations = [];
17
-
18
- // Based on accuracy
19
- if (results.accuracy < 0.7) {
20
- recommendations.push({
21
- icon: "⚠️",
22
- text: "Accuracy is low. Consider decreasing noise multiplier or increasing the number of epochs."
23
- });
24
- }
25
-
26
- // Based on privacy budget
27
- if (results.privacyBudget > 8) {
28
- recommendations.push({
29
- icon: "🔓",
30
- text: "Privacy budget is high. Consider increasing noise multiplier or reducing epochs for stronger privacy."
31
- });
32
- } else if (results.privacyBudget < 1 && results.accuracy < 0.85) {
33
- recommendations.push({
34
- icon: "🔒",
35
- text: "Very strong privacy may be limiting accuracy. Consider a slight increase in privacy budget."
36
- });
37
- }
38
-
39
- // Based on clipping
40
- if (config.dpParams.clipNorm < 0.5) {
41
- recommendations.push({
42
- icon: "✂️",
43
- text: "Clipping norm is very low. This might be over-clipping gradients and limiting learning."
44
- });
45
- }
46
-
47
- // Based on batch size
48
- if (config.dpParams.batchSize < 32) {
49
- recommendations.push({
50
- icon: "📊",
51
- text: "Small batch size can make training with DP-SGD unstable. Consider increasing batch size."
52
- });
53
- }
54
-
55
- // Add a generic recommendation if none were generated
56
- if (recommendations.length === 0) {
57
- recommendations.push({
58
- icon: "👍",
59
- text: "Current configuration seems well-balanced. Experiment with small parameter changes to optimize further."
60
- });
61
- }
62
-
63
- return recommendations;
64
- };
65
-
66
- // If no results, show placeholder
67
- if (!results) {
68
- return (
69
- <div className="results-panel">
70
- <h2 className="panel-title">Results</h2>
71
- <div className="no-results">
72
- <p className="placeholder-text">Run training to see results here</p>
73
- <div className="placeholder-icon">📊</div>
74
- </div>
75
- </div>
76
- );
77
- }
78
-
79
- return (
80
- <div className="results-panel">
81
- <div className="results-header">
82
- <h2 className="panel-title">Results</h2>
83
- <div className="view-toggle">
84
- <button
85
- className={`toggle-button ${showFinalMetrics ? 'active' : ''}`}
86
- onClick={() => setShowFinalMetrics(true)}
87
- >
88
- Final Metrics
89
- </button>
90
- <button
91
- className={`toggle-button ${!showFinalMetrics ? 'active' : ''}`}
92
- onClick={() => setShowFinalMetrics(false)}
93
- >
94
- Recommendations
95
- </button>
96
- </div>
97
- </div>
98
-
99
- {showFinalMetrics ? (
100
- <div className="metrics-section">
101
- <div className="metrics-grid">
102
- <div className="metric-card">
103
- <div className="metric-value primary">
104
- {formatMetric(results.accuracy * 100, 1)}%
105
- </div>
106
- <div className="metric-label">
107
- Accuracy
108
- <TechnicalTooltip text="Model performance on test data. Higher values are better." />
109
- </div>
110
- </div>
111
-
112
- <div className="metric-card">
113
- <div className="metric-value">
114
- {formatMetric(results.loss, 3)}
115
- </div>
116
- <div className="metric-label">
117
- Loss
118
- <TechnicalTooltip text="Final training loss. Lower values generally indicate better model fit." />
119
- </div>
120
- </div>
121
-
122
- <div className="metric-card">
123
- <div className={`metric-value privacy-budget ${getPrivacyClass(results.privacyBudget)}`}>
124
- ε = {formatMetric(results.privacyBudget, 2)}
125
- </div>
126
- <div className="metric-label">
127
- Privacy Budget
128
- <TechnicalTooltip text="Final privacy loss (ε). Lower values mean stronger privacy guarantees." />
129
- </div>
130
- </div>
131
-
132
- <div className="metric-card">
133
- <div className="metric-value">
134
- {formatMetric(results.trainingTime, 1)}s
135
- </div>
136
- <div className="metric-label">
137
- Training Time
138
- <TechnicalTooltip text="Total time spent on training, including privacy mechanisms." />
139
- </div>
140
- </div>
141
- </div>
142
-
143
- <div className="privacy-utility-summary">
144
- <h3>Privacy-Utility Trade-off</h3>
145
- <div className="tradeoff-meter">
146
- <div className="meter-bar">
147
- <div
148
- className="utility-indicator"
149
- style={{ left: `${Math.min(results.accuracy * 100, 100)}%` }}
150
- >
151
- <span className="indicator-label">Utility</span>
152
- </div>
153
- <div
154
- className="privacy-indicator"
155
- style={{ right: `${Math.min(100 - (results.privacyBudget / 10 * 100), 100)}%` }}
156
- >
157
- <span className="indicator-label">Privacy</span>
158
- </div>
159
- </div>
160
- <div className="meter-explanation">
161
- <p>
162
- This model achieved {formatMetric(results.accuracy * 100, 1)}% accuracy with a privacy budget of ε={formatMetric(results.privacyBudget, 2)}.
163
- {getTradeoffExplanation(results.accuracy, results.privacyBudget)}
164
- </p>
165
- </div>
166
- </div>
167
- </div>
168
- </div>
169
- ) : (
170
- <div className="recommendation-section">
171
- <h3>Recommendations</h3>
172
- <ul className="recommendations-list">
173
- {generateRecommendations(results, config).map((rec, idx) => (
174
- <li key={idx} className="recommendation-item">
175
- <span className="recommendation-icon">{rec.icon}</span>
176
- <span className="recommendation-text">{rec.text}</span>
177
- </li>
178
- ))}
179
- </ul>
180
- </div>
181
- )}
182
- </div>
183
- );
184
- };
185
-
186
- // Learning Hub Component
187
- const LearningHub = ({ userRole }) => {
188
- const [activeStep, setActiveStep] = React.useState('introduction');
189
-
190
- const steps = [
191
- { id: 'introduction', title: 'Introduction to Differential Privacy', completed: true },
192
- { id: 'dp-concepts', title: 'Core DP Concepts', completed: true },
193
- { id: 'sgd-basics', title: 'Stochastic Gradient Descent Refresher', completed: false },
194
- { id: 'dpsgd-intro', title: 'DP-SGD: Core Modifications', completed: false },
195
- { id: 'parameters', title: 'Hyperparameter Deep Dive', completed: false },
196
- { id: 'privacy-accounting', title: 'Privacy Accounting', completed: false }
197
- ];
198
-
199
- const stepContent = {
200
- 'introduction': (
201
- <div>
202
- <h2>Introduction to Differential Privacy</h2>
203
- <p>Differential Privacy (DP) is a mathematical framework that provides strong privacy guarantees when performing analyses on sensitive data. It ensures that the presence or absence of any single individual's data has a minimal effect on the output of an analysis.</p>
204
-
205
- <h3>Why is Differential Privacy Important?</h3>
206
- <p>Traditional anonymization techniques often fail to protect privacy. With enough auxiliary information, it's possible to re-identify individuals in supposedly "anonymized" datasets. Differential privacy addresses this by adding carefully calibrated noise to the analysis process.</p>
207
-
208
- <h3>The Privacy-Utility Trade-off</h3>
209
- <p>There's an inherent trade-off between privacy and utility (accuracy) in DP. More privacy means more noise, which typically reduces accuracy. The challenge is finding the right balance for your specific application.</p>
210
-
211
- <div className="role-specific-content">
212
- <h4>For {userRole === 'ml-engineer' ? 'ML Engineers' :
213
- userRole === 'data-scientist' ? 'Data Scientists' :
214
- userRole === 'privacy-officer' ? 'Privacy Officers' : 'Business Stakeholders'}</h4>
215
-
216
- {userRole === 'ml-engineer' && (
217
- <p>As an ML engineer, you'll need to understand how to implement DP-SGD in your machine learning pipelines, balancing privacy guarantees with model performance.</p>
218
- )}
219
-
220
- {userRole === 'data-scientist' && (
221
- <p>As a data scientist, focus on understanding how different privacy parameters affect model accuracy and how to tune these for optimal performance.</p>
222
- )}
223
-
224
- {userRole === 'privacy-officer' && (
225
- <p>As a privacy officer, pay special attention to the formal privacy guarantees and how DP can help meet regulatory requirements like GDPR or HIPAA.</p>
226
- )}
227
-
228
- {userRole === 'business-stakeholder' && (
229
- <p>As a business stakeholder, focus on understanding the trade-offs between privacy and utility, and how these impact business metrics and compliance requirements.</p>
230
- )}
231
- </div>
232
- </div>
233
- ),
234
-
235
- 'dp-concepts': (
236
- <div>
237
- <h2>Core Differential Privacy Concepts</h2>
238
-
239
- <h3>The Formal Definition</h3>
240
- <p>A mechanism M is (ε,δ)-differentially private if for all neighboring datasets D and D' (differing in one record), and for all possible outputs S:</p>
241
- <div style={{ padding: '10px', backgroundColor: '#f5f7fa', borderRadius: '4px', fontFamily: 'monospace' }}>
242
- P(M(D) ∈ S) ≤ e^ε × P(M(D') ∈ S) + δ
243
- </div>
244
-
245
- <h3>Key Parameters</h3>
246
- <p><strong>ε (epsilon)</strong>: The privacy budget. Lower values mean stronger privacy but typically lower utility.</p>
247
- <p><strong>δ (delta)</strong>: The probability of the privacy guarantee being broken. Usually set very small (e.g., 10^-5).</p>
248
-
249
- <h3>Differential Privacy Mechanisms</h3>
250
- <p><strong>Laplace Mechanism</strong>: Adds noise from a Laplace distribution to numeric queries.</p>
251
- <p><strong>Gaussian Mechanism</strong>: Adds noise from a Gaussian (normal) distribution. This is used in DP-SGD.</p>
252
- <p><strong>Exponential Mechanism</strong>: Used for non-numeric outputs, selects an output based on a probability distribution.</p>
253
-
254
- <h3>Privacy Accounting</h3>
255
- <p>When you apply multiple differentially private operations, the privacy loss (ε) accumulates. This is known as composition.</p>
256
- <p>Advanced composition theorems and privacy accountants help track the total privacy spend.</p>
257
- </div>
258
- ),
259
-
260
- 'sgd-basics': (
261
- <div>
262
- <h2>Stochastic Gradient Descent Refresher</h2>
263
-
264
- <h3>Standard SGD</h3>
265
- <p>Stochastic Gradient Descent (SGD) is an optimization algorithm used to train machine learning models by iteratively updating parameters based on gradients computed from mini-batches of data.</p>
266
-
267
- <h3>The Basic Update Rule</h3>
268
- <p>The standard SGD update for a batch B is:</p>
269
- <div style={{ padding: '10px', backgroundColor: '#f5f7fa', borderRadius: '4px', fontFamily: 'monospace' }}>
270
- θ ← θ - η∇L(θ; B)
271
- </div>
272
- <p>Where:</p>
273
- <ul>
274
- <li>θ represents the model parameters</li>
275
- <li>η is the learning rate</li>
276
- <li>∇L(θ; B) is the average gradient of the loss over the batch B</li>
277
- </ul>
278
-
279
- <h3>Privacy Concerns with Standard SGD</h3>
280
- <p>Standard SGD can leak information about individual training examples through the gradients. For example:</p>
281
- <ul>
282
- <li>Gradients might be larger for outliers or unusual examples</li>
283
- <li>Model memorization of sensitive data can be extracted through attacks</li>
284
- <li>Gradient values can be used in reconstruction attacks</li>
285
- </ul>
286
-
287
- <p>These privacy concerns motivate the need for differentially private training methods.</p>
288
- </div>
289
- ),
290
-
291
- 'dpsgd-intro': (
292
- <div>
293
- <h2>DP-SGD: Core Modifications</h2>
294
-
295
- <h3>How DP-SGD Differs from Standard SGD</h3>
296
- <p>Differentially Private SGD modifies standard SGD in two key ways:</p>
297
-
298
- <div style={{ display: 'flex', gap: '20px', margin: '20px 0' }}>
299
- <div style={{ flex: 1, padding: '15px', backgroundColor: '#e3f2fd', borderRadius: '8px' }}>
300
- <h4>1. Per-Sample Gradient Clipping</h4>
301
- <p>Compute gradients for each example individually, then clip their L2 norm to a threshold C.</p>
302
- <p>This limits the influence of any single training example on the model update.</p>
303
- </div>
304
-
305
- <div style={{ flex: 1, padding: '15px', backgroundColor: '#fff8e1', borderRadius: '8px' }}>
306
- <h4>2. Noise Addition</h4>
307
- <p>Add Gaussian noise to the sum of clipped gradients before applying the update.</p>
308
- <p>The noise scale is proportional to the clipping threshold and the noise multiplier.</p>
309
- </div>
310
- </div>
311
-
312
- <h3>The DP-SGD Update Rule</h3>
313
- <p>The DP-SGD update can be summarized as:</p>
314
- <ol>
315
- <li>Compute per-sample gradients: g<sub>i</sub> = ∇L(θ; x<sub>i</sub>)</li>
316
- <li>Clip each gradient: g̃<sub>i</sub> = g<sub>i</sub> × min(1, C/||g<sub>i</sub>||<sub>2</sub>)</li>
317
- <li>Add noise: ḡ = (1/|B|) × (∑g̃<sub>i</sub> + N(0, σ²C²I))</li>
318
- <li>Update parameters: θ ← θ - η × ḡ</li>
319
- </ol>
320
-
321
- <p>Where:</p>
322
- <ul>
323
- <li>C is the clipping norm</li>
324
- <li>σ is the noise multiplier</li>
325
- <li>B is the batch</li>
326
- </ul>
327
- </div>
328
- ),
329
-
330
- 'parameters': (
331
- <div>
332
- <h2>Hyperparameter Deep Dive</h2>
333
-
334
- <p>DP-SGD introduces several new hyperparameters that need to be tuned carefully:</p>
335
-
336
- <h3>Clipping Norm (C)</h3>
337
- <p>The maximum allowed L2 norm for any individual gradient.</p>
338
- <ul>
339
- <li><strong>Too small:</strong> Gradients are over-clipped, limiting learning</li>
340
- <li><strong>Too large:</strong> Requires more noise to achieve the same privacy guarantee</li>
341
- <li><strong>Typical range:</strong> 0.1 to 10.0, depending on the dataset and model</li>
342
- </ul>
343
-
344
- <h3>Noise Multiplier (σ)</h3>
345
- <p>Controls the amount of noise added to the gradients.</p>
346
- <ul>
347
- <li><strong>Higher σ:</strong> Better privacy, worse utility</li>
348
- <li><strong>Lower σ:</strong> Better utility, worse privacy</li>
349
- <li><strong>Typical range:</strong> 0.5 to 2.0 for most practical applications</li>
350
- </ul>
351
-
352
- <h3>Batch Size</h3>
353
- <p>Affects both training dynamics and privacy accounting.</p>
354
- <ul>
355
- <li><strong>Larger batches:</strong> Reduce variance from noise, but change sampling probability</li>
356
- <li><strong>Smaller batches:</strong> More update steps, potentially consuming more privacy budget</li>
357
- <li><strong>Typical range:</strong> 64 to 1024, larger than standard SGD</li>
358
- </ul>
359
-
360
- <h3>Learning Rate (η)</h3>
361
- <p>May need adjustment compared to non-private training.</p>
362
- <ul>
363
- <li><strong>DP-SGD often requires:</strong> Lower learning rates or careful scheduling</li>
364
- <li><strong>Reason:</strong> Added noise can destabilize training with high learning rates</li>
365
- </ul>
366
-
367
- <h3>Number of Epochs</h3>
368
- <p>More epochs consume more privacy budget.</p>
369
- <ul>
370
- <li><strong>Trade-off:</strong> More training vs. privacy budget consumption</li>
371
- <li><strong>Early stopping:</strong> Often beneficial for balancing accuracy and privacy</li>
372
- </ul>
373
- </div>
374
- ),
375
-
376
- 'privacy-accounting': (
377
- <div>
378
- <h2>Privacy Accounting</h2>
379
-
380
- <h3>Tracking Privacy Budget</h3>
381
- <p>Privacy accounting is the process of keeping track of the total privacy loss (ε) throughout training.</p>
382
-
383
- <h3>Common Methods</h3>
384
- <div style={{ display: 'flex', flexDirection: 'column', gap: '15px', margin: '15px 0' }}>
385
- <div style={{ padding: '15px', backgroundColor: '#f5f7fa', borderRadius: '8px' }}>
386
- <h4>Moment Accountant</h4>
387
- <p>Used in the original DP-SGD paper, provides tight bounds on the privacy loss.</p>
388
- <p>Tracks the moments of the privacy loss random variable.</p>
389
- </div>
390
-
391
- <div style={{ padding: '15px', backgroundColor: '#f5f7fa', borderRadius: '8px' }}>
392
- <h4>Rényi Differential Privacy (RDP)</h4>
393
- <p>Alternative accounting method based on Rényi divergence.</p>
394
- <p>Often used in modern implementations like TensorFlow Privacy and Opacus.</p>
395
- </div>
396
-
397
- <div style={{ padding: '15px', backgroundColor: '#f5f7fa', borderRadius: '8px' }}>
398
- <h4>Analytical Gaussian Mechanism</h4>
399
- <p>Simpler method for specific mechanisms like the Gaussian Mechanism.</p>
400
- <p>Less tight bounds but easier to compute.</p>
401
- </div>
402
- </div>
403
-
404
- <h3>Privacy Budget Allocation</h3>
405
- <p>With a fixed privacy budget (ε), you must decide how to allocate it:</p>
406
- <ul>
407
- <li><strong>Fixed noise, variable epochs:</strong> Set noise level, train until budget is exhausted</li>
408
- <li><strong>Fixed epochs, variable noise:</strong> Set desired epochs, calculate required noise</li>
409
- <li><strong>Advanced techniques:</strong> Privacy filters, odometers, and adaptive mechanisms</li>
410
- </ul>
411
-
412
- <h3>Practical Implementation</h3>
413
- <p>In practice, privacy accounting is handled by libraries like:</p>
414
- <ul>
415
- <li>TensorFlow Privacy</li>
416
- <li>PyTorch Opacus</li>
417
- <li>Diffprivlib (IBM)</li>
418
- </ul>
419
- </div>
420
- )
421
- };
422
-
423
- return (
424
- <div className="learning-hub">
425
- <h1 className="section-title">Learning Hub</h1>
426
-
427
- <div className="learning-container">
428
- <div className="learning-sidebar">
429
- <h2 className="panel-title">Differential Privacy in ML</h2>
430
-
431
- <ul className="learning-steps">
432
- {steps.map(step => (
433
- <li
434
- key={step.id}
435
- className={`learning-step ${activeStep === step.id ? 'active' : ''}`}
436
- onClick={() => setActiveStep(step.id)}
437
- >
438
- <div className={`step-indicator ${step.completed ? 'completed' : ''} ${activeStep === step.id ? 'active' : ''}`}></div>
439
- <span className={`step-title ${activeStep === step.id ? 'active' : ''} ${step.completed ? 'completed' : ''}`}>
440
- {step.title}
441
- </span>
442
- </li>
443
- ))}
444
- </ul>
445
-
446
- <div className="role-info" style={{ marginTop: '20px', padding: '15px', backgroundColor: '#f5f7fa', borderRadius: '8px' }}>
447
- <p><strong>Content tailored for:</strong> {userRole === 'ml-engineer' ? 'ML Engineers' :
448
- userRole === 'data-scientist' ? 'Data Scientists' :
449
- userRole === 'privacy-officer' ? 'Privacy Officers' : 'Business Stakeholders'}</p>
450
- <p style={{ fontSize: 'var(--font-size-small)', marginTop: '8px' }}>
451
- You can change your role in the top navigation bar.
452
- </p>
453
- </div>
454
- </div>
455
-
456
- <div className="learning-content">
457
- {stepContent[activeStep]}
458
- </div>
459
- </div>
460
- </div>
461
- );
462
- };
463
-
464
- // Main App Component
465
- const App = () => {
466
- // State for active section
467
- const [activeSection, setActiveSection] = React.useState('learning-hub');
468
-
469
- // State for user role
470
- const [userRole, setUserRole] = React.useState('ml-engineer');
471
- const [showRoleSelector, setShowRoleSelector] = React.useState(false);
472
-
473
- // DP-SGD Configuration
474
- const [config, setConfig] = React.useState({
475
- dataset: 'mnist',
476
- modelArchitecture: 'simple-cnn',
477
- dpParams: {
478
- clipNorm: 1.0,
479
- noiseMultiplier: 1.0,
480
- batchSize: 64,
481
- learningRate: 0.01,
482
- epochs: 5
483
- }
484
- });
485
-
486
- // Training state
487
- const [isTraining, setIsTraining] = React.useState(false);
488
- const [trainingProgress, setTrainingProgress] = React.useState({
489
- currentEpoch: 0,
490
- accuracy: [],
491
- loss: [],
492
- privacyBudgetUsed: []
493
- });
494
-
495
- // Results state
496
- const [results, setResults] = React.useState(null);
497
-
498
- // Roles configuration
499
- const roles = [
500
- { id: 'ml-engineer', label: 'ML Engineer' },
501
- { id: 'data-scientist', label: 'Data Scientist' },
502
- { id: 'privacy-officer', label: 'Privacy/Legal' },
503
- { id: 'business-stakeholder', label: 'Business Stakeholder' }
504
- ];
505
-
506
- const currentRole = roles.find(role => role.id === userRole) || roles[0];
507
-
508
- // Handle role selection
509
- const handleRoleSelect = (roleId) => {
510
- setUserRole(roleId);
511
- setShowRoleSelector(false);
512
- };
513
-
514
- // Update configuration
515
- const handleConfigChange = (newConfig) => {
516
- setConfig(prevConfig => ({
517
- ...prevConfig,
518
- ...newConfig
519
- }));
520
- };
521
-
522
- // Update DP parameters
523
- const handleDpParamChange = (paramName, value) => {
524
- setConfig(prevConfig => ({
525
- ...prevConfig,
526
- dpParams: {
527
- ...prevConfig.dpParams,
528
- [paramName]: value
529
- }
530
- }));
531
- };
532
-
533
- // Start training process
534
- const handleStartTraining = async () => {
535
- if (isTraining) return;
536
-
537
- setIsTraining(true);
538
- setTrainingProgress({
539
- currentEpoch: 0,
540
- accuracy: [],
541
- loss: [],
542
- privacyBudgetUsed: []
543
- });
544
- setResults(null);
545
-
546
- try {
547
- // Initialize DP-SGD
548
- const dpsgd = new DPSGD({
549
- learningRate: config.dpParams.learningRate,
550
- clipNorm: config.dpParams.clipNorm,
551
- noiseMultiplier: config.dpParams.noiseMultiplier,
552
- batchSize: config.dpParams.batchSize
553
- });
554
-
555
- // Simulate training
556
- const trainingResult = await dpsgd.train(config, (progress) => {
557
- setTrainingProgress(prevProgress => ({
558
- ...prevProgress,
559
- currentEpoch: progress.currentEpoch,
560
- accuracy: [...prevProgress.accuracy, progress.accuracy],
561
- loss: [...prevProgress.loss, progress.loss],
562
- privacyBudgetUsed: [...prevProgress.privacyBudgetUsed, progress.privacyBudgetUsed]
563
- }));
564
- });
565
-
566
- setResults(trainingResult);
567
- } catch (error) {
568
- console.error("Training error:", error);
569
- } finally {
570
- setIsTraining(false);
571
- }
572
- };
573
-
574
- // Stop ongoing training
575
- const handleStopTraining = () => {
576
- if (!isTraining) return;
577
- setIsTraining(false);
578
- };
579
-
580
- // Calculate estimated privacy budget
581
- const calculatePrivacyBudget = () => {
582
- // Simplified Analytical Gaussian method for educational purposes
583
- const { noiseMultiplier, batchSize, epochs } = config.dpParams;
584
- const samplingRate = batchSize / 60000; // Assuming MNIST size
585
- const steps = epochs * (1 / samplingRate);
586
- const delta = 1e-5; // Common delta value
587
-
588
- const c = Math.sqrt(2 * Math.log(1.25 / delta));
589
- const epsilon = (c * samplingRate * Math.sqrt(steps)) / noiseMultiplier;
590
-
591
- return Math.min(epsilon, 100); // Cap at 100
592
- };
593
-
594
- // Estimated privacy budget
595
- const expectedPrivacyBudget = calculatePrivacyBudget();
596
-
597
- return (
598
- <div className="app-container">
599
- <header className="main-header">
600
- <div className="header-container">
601
- <div className="logo-container">
602
- <span className="logo">DP-SGD Explorer</span>
603
- <span className="tagline">Interactive Learning & Experimentation</span>
604
- </div>
605
-
606
- <nav className="main-nav">
607
- <ul className="nav-list">
608
- <li>
609
- <div
610
- className={`nav-link ${activeSection === 'learning-hub' ? 'active' : ''}`}
611
- onClick={() => setActiveSection('learning-hub')}
612
- >
613
- <span className="nav-icon">📚</span>
614
- Learning Hub
615
- </div>
616
- </li>
617
- <li>
618
- <div
619
- className={`nav-link ${activeSection === 'playground' ? 'active' : ''}`}
620
- onClick={() => setActiveSection('playground')}
621
- >
622
- <span className="nav-icon">🧪</span>
623
- Playground
624
- </div>
625
- </li>
626
- </ul>
627
- </nav>
628
-
629
- <div className="user-role-selector">
630
- <button
631
- className="role-button"
632
- onClick={() => setShowRoleSelector(!showRoleSelector)}
633
- >
634
- <span className="current-role">{currentRole.label}</span>
635
- <span className="role-icon">⌄</span>
636
- </button>
637
-
638
- {showRoleSelector && (
639
- <div className="role-dropdown">
640
- <ul>
641
- {roles.map(role => (
642
- <li key={role.id}>
643
- <button
644
- className={role.id === userRole ? 'active' : ''}
645
- onClick={() => handleRoleSelect(role.id)}
646
- >
647
- {role.label}
648
- {role.id === userRole && <span className="check-icon">✓</span>}
649
- </button>
650
- </li>
651
- ))}
652
- </ul>
653
- <div className="role-info">
654
- <p>Selecting a role customizes content to your needs.</p>
655
- </div>
656
- </div>
657
- )}
658
- </div>
659
- </div>
660
- </header>
661
-
662
- <main className="main-content">
663
- {activeSection === 'learning-hub' && (
664
- <LearningHub userRole={userRole} />
665
- )}
666
-
667
- {activeSection === 'playground' && (
668
- <div className="hands-on-lab">
669
- <h1 className="section-title">DP-SGD Interactive Playground</h1>
670
-
671
- <div className="lab-container">
672
- <div className="lab-sidebar">
673
- <ModelConfigurationPanel
674
- config={config}
675
- onConfigChange={handleConfigChange}
676
- disabled={isTraining}
677
- />
678
-
679
- <ParameterControlPanel
680
- dpParams={config.dpParams}
681
- onParamChange={handleDpParamChange}
682
- expectedPrivacyBudget={expectedPrivacyBudget}
683
- disabled={isTraining}
684
- />
685
-
686
- <div className="control-buttons">
687
- <button
688
- className={`primary-button ${isTraining ? 'running' : ''}`}
689
- onClick={isTraining ? handleStopTraining : handleStartTraining}
690
- aria-label={isTraining ? "Stop Training" : "Start Training"}
691
- >
692
- {isTraining ? 'Stop' : 'Run Training'}
693
- </button>
694
- </div>
695
- </div>
696
-
697
- <div className="lab-main">
698
- <div className="visualizer-container">
699
- <TrainingVisualizer
700
- progress={trainingProgress}
701
- isTraining={isTraining}
702
- config={config}
703
- />
704
- </div>
705
-
706
- <div className="results-container">
707
- <ResultsPanel
708
- results={results}
709
- config={config}
710
- />
711
- </div>
712
- </div>
713
- </div>
714
- </div>
715
- )}
716
- </main>
717
-
718
- <footer className="main-footer">
719
- <p>DP-SGD Explorer - An Educational Tool for Differential Privacy in Machine Learning</p>
720
- <p>© {new Date().getFullYear()} - For educational purposes</p>
721
- </footer>
722
- </div>
723
- );
724
- };
725
-
726
- // Render the App
727
- const root = ReactDOM.createRoot(document.getElementById('root'));
728
- root.render(<App />);
729
- </script>
730
- </body>
731
- </html> .step-title {
732
- font-weight: 500;
733
- color: var(--text-secondary);
734
- }
735
-
736
- .step-title.active {
737
- color: var(--primary-color);
738
- }
739
-
740
- .step-title.completed {
741
- color: var(--text-primary);
742
- }
743
-
744
- /* Footer */
745
- .main-footer {
746
- background-color: var(--primary-dark);
747
- color: var(--text-light);
748
- padding: var(--spacing-lg);
749
- text-align: center;
750
- margin-top: var(--spacing-xxl);
751
- }
752
-
753
- /* Responsive Adjustments */
754
- @media screen and (max-width: 1200px) {
755
- .metrics-grid {
756
- grid-template-columns: repeat(2, 1fr);
757
- }
758
-
759
- .learning-container {
760
- grid-template-columns: 1fr;
761
- }
762
- }
763
-
764
- @media screen and (max-width: 900px) {
765
- .lab-container {
766
- grid-template-columns: 1fr;
767
- }
768
-
769
- .nav-list {
770
- gap: var(--spacing-md);
771
- }
772
-
773
- .nav-link {
774
- padding: var(--spacing-sm);
775
- }
776
- }
777
-
778
- @media screen and (max-width: 600px) {
779
- .preset-buttons {
780
- grid-template-columns: 1fr;
781
- }
782
-
783
- .metrics-grid {
784
- grid-template-columns: 1fr;
785
- }
786
-
787
- .nav-icon {
788
- display: none;
789
- }
790
- }
791
- </style>
792
- </head>
793
- <body>
794
- <div id="root"></div>
795
-
796
- <script type="text/babel">
797
- // ========== Utility Components ==========
798
-
799
- // Technical Tooltip Component
800
- const TechnicalTooltip = ({ text, wide = false }) => {
801
- const [isVisible, setIsVisible] = React.useState(false);
802
- const tooltipRef = React.useRef(null);
803
-
804
- // Handle clicks outside to close tooltip
805
- React.useEffect(() => {
806
- const handleClickOutside = (event) => {
807
- if (tooltipRef.current && !tooltipRef.current.contains(event.target)) {
808
- setIsVisible(false);
809
- }
810
- };
811
-
812
- if (isVisible) {
813
- document.addEventListener('mousedown', handleClickOutside);
814
- }
815
-
816
- return () => {
817
- document.removeEventListener('mousedown', handleClickOutside);
818
- };
819
- }, [isVisible]);
820
-
821
- return (
822
- <div className="tooltip-container" ref={tooltipRef}>
823
- <button
824
- className="tooltip-icon"
825
- onClick={() => setIsVisible(!isVisible)}
826
- aria-label="Show explanation"
827
- type="button"
828
- >
829
- ?
830
- </button>
831
-
832
- {isVisible && (
833
- <div
834
- className={`tooltip-content ${wide ? 'wide' : ''}`}
835
- role="tooltip"
836
- >
837
- {text}
838
- </div>
839
- )}
840
- </div>
841
- );
842
- };
843
-
844
- // ========== Core DPSGD Components ==========
845
-
846
- // DPSGD Class - Simplified for educational purposes
847
- class DPSGD {
848
- constructor(config) {
849
- this.learningRate = config.learningRate || 0.01;
850
- this.clipNorm = config.clipNorm || 1.0;
851
- this.noiseMultiplier = config.noiseMultiplier || 1.0;
852
- this.batchSize = config.batchSize || 32;
853
- this.onBatchEnd = config.onBatchEnd || (() => {});
854
-
855
- // Privacy tracking
856
- this.stepCount = 0;
857
- }
858
-
859
- // Simulate training for educational purposes
860
- async train(config, onProgress) {
861
- const { epochs = 5 } = config;
862
-
863
- // History to track metrics
864
- const history = {
865
- accuracy: [],
866
- loss: [],
867
- privacyBudgetUsed: []
868
- };
869
-
870
- // Reset step counter
871
- this.stepCount = 0;
872
-
873
- // Simulate training loop
874
- for (let epoch = 0; epoch < epochs; epoch++) {
875
- console.log(`Starting epoch ${epoch + 1}/${epochs}`);
876
-
877
- // Calculate synthetic metrics (simulated)
878
- const baseAccuracy = 0.75; // Starting point
879
- const noisePenalty = this.noiseMultiplier * 0.05; // Higher noise = lower accuracy
880
- const clipPenalty = Math.abs(1.0 - this.clipNorm) * 0.03; // Clipping too high or too low hurts
881
- const epochBonus = Math.min(epoch * 0.05, 0.15); // Improvement with epochs
882
-
883
- // Calculate accuracy with some randomness
884
- const accuracy = Math.min(
885
- 0.98, // max possible
886
- baseAccuracy - noisePenalty - clipPenalty + epochBonus + (Math.random() * 0.02)
887
- );
888
-
889
- // Calculate loss (inverse relationship with accuracy)
890
- const loss = Math.max(0.1, 1.0 - accuracy + (Math.random() * 0.05));
891
-
892
- // Calculate privacy budget used based on parameters
893
- const samplingRate = this.batchSize / 60000; // Assume MNIST size
894
- const privacyBudgetUsed = this.calculatePrivacyBudget(
895
- this.noiseMultiplier,
896
- samplingRate,
897
- epoch + 1
898
- );
899
-
900
- // Store metrics
901
- history.accuracy.push(accuracy);
902
- history.loss.push(loss);
903
- history.privacyBudgetUsed.push(privacyBudgetUsed);
904
-
905
- // Report progress after a slight delay to simulate computation
906
- await new Promise(resolve => setTimeout(resolve, 500));
907
- onProgress({
908
- currentEpoch: epoch,
909
- accuracy: accuracy,
910
- loss: loss,
911
- privacyBudgetUsed: privacyBudgetUsed
912
- });
913
-
914
- this.stepCount += Math.floor(60000 / this.batchSize); // Simulate steps per epoch
915
- }
916
-
917
- // Return final results
918
- return {
919
- accuracy: history.accuracy[history.accuracy.length - 1],
920
- loss: history.loss[history.loss.length - 1],
921
- privacyBudget: history.privacyBudgetUsed[history.privacyBudgetUsed.length - 1],
922
- trainingTime: epochs * 2.5, // Simulated time in seconds
923
- history: history
924
- };
925
- }
926
-
927
- // Calculate privacy budget (simplified Analytical Gaussian method)
928
- calculatePrivacyBudget(noiseMultiplier, samplingRate, epochs) {
929
- const steps = epochs * (1 / samplingRate);
930
- const delta = 1e-5; // Common delta value
931
-
932
- // Simple formula for analytical Gaussian (simplified)
933
- const c = Math.sqrt(2 * Math.log(1.25 / delta));
934
- const epsilon = (c * samplingRate * Math.sqrt(steps)) / noiseMultiplier;
935
-
936
- return Math.min(epsilon, 100); // Cap at 100 to prevent overflow
937
- }
938
- }
939
-
940
- // ========== Page Components ==========
941
-
942
- // Model Configuration Panel
943
- const ModelConfigurationPanel = ({ config, onConfigChange, disabled }) => {
944
- const datasets = [
945
- { id: 'mnist', name: 'MNIST Digits', description: 'Handwritten digits (0-9)', examples: 60000, dimensions: '28×28 px' },
946
- { id: 'fashion-mnist', name: 'Fashion MNIST', description: 'Clothing items (10 classes)', examples: 60000, dimensions: '28×28 px' },
947
- { id: 'cifar10', name: 'CIFAR-10', description: 'Natural images (10 classes)', examples: 50000, dimensions: '32×32 px' },
948
- { id: 'synthetic', name: 'Synthetic Data', description: 'Generated tabular data', examples: 10000, dimensions: '20 features' }
949
- ];
950
-
951
- const modelArchitectures = [
952
- { id: 'simple-mlp', name: 'Simple MLP', description: 'Multi-layer perceptron with 2 hidden layers', params: '15K', complexity: 'Low' },
953
- { id: 'simple-cnn', name: 'Simple CNN', description: 'Convolutional network with 2 conv layers', params: '120K', complexity: 'Medium' },
954
- { id: 'advanced-cnn', name: 'Advanced CNN', description: 'Deeper CNN with residual connections', params: '1.2M', complexity: 'High' }
955
- ];
956
-
957
- const handleDatasetChange = (e) => {
958
- onConfigChange({ dataset: e.target.value });
959
- };
960
-
961
- const handleModelChange = (e) => {
962
- onConfigChange({ modelArchitecture: e.target.value });
963
- };
964
-
965
- const currentDataset = datasets.find(d => d.id === config.dataset) || datasets[0];
966
- const currentModel = modelArchitectures.find(m => m.id === config.modelArchitecture) || modelArchitectures[0];
967
-
968
- return (
969
- <div className="model-configuration-panel">
970
- <h2 className="panel-title">Model Configuration</h2>
971
-
972
- <div className="config-section">
973
- <div className="section-header">
974
- <h3>Dataset</h3>
975
- <TechnicalTooltip text="The dataset used for training affects privacy budget calculations and model accuracy." />
976
- </div>
977
-
978
- <select
979
- id="dataset-select"
980
- value={config.dataset}
981
- onChange={handleDatasetChange}
982
- disabled={disabled}
983
- className="config-select"
984
- >
985
- {datasets.map(dataset => (
986
- <option key={dataset.id} value={dataset.id}>
987
- {dataset.name}
988
- </option>
989
- ))}
990
- </select>
991
-
992
- <div className="dataset-info">
993
- <div className="info-item">
994
- <span className="info-label">Description:</span>
995
- <span className="info-value">{currentDataset.description}</span>
996
- </div>
997
- <div className="info-item">
998
- <span className="info-label">Training Examples:</span>
999
- <span className="info-value">{currentDataset.examples.toLocaleString()}</span>
1000
- </div>
1001
- <div className="info-item">
1002
- <span className="info-label">Dimensions:</span>
1003
- <span className="info-value">{currentDataset.dimensions}</span>
1004
- </div>
1005
- </div>
1006
- </div>
1007
-
1008
- <div className="config-section">
1009
- <div className="section-header">
1010
- <h3>Model Architecture</h3>
1011
- <TechnicalTooltip text="The model architecture affects training time, capacity to learn, and resilience to noise." />
1012
- </div>
1013
-
1014
- <select
1015
- id="model-select"
1016
- value={config.modelArchitecture}
1017
- onChange={handleModelChange}
1018
- disabled={disabled}
1019
- className="config-select"
1020
- >
1021
- {modelArchitectures.map(model => (
1022
- <option key={model.id} value={model.id}>
1023
- {model.name}
1024
- </option>
1025
- ))}
1026
- </select>
1027
-
1028
- <div className="model-info">
1029
- <div className="info-item">
1030
- <span className="info-label">Description:</span>
1031
- <span className="info-value">{currentModel.description}</span>
1032
- </div>
1033
- <div className="info-item">
1034
- <span className="info-label">Parameters:</span>
1035
- <span className="info-value">{currentModel.params}</span>
1036
- </div>
1037
- <div className="info-item">
1038
- <span className="info-label">Complexity:</span>
1039
- <span className="info-value">
1040
- <span className={`complexity-badge ${currentModel.complexity.toLowerCase()}`}>
1041
- {currentModel.complexity}
1042
- </span>
1043
- </span>
1044
- </div>
1045
- </div>
1046
- </div>
1047
-
1048
- <div className="config-section preset-section">
1049
- <div className="section-header">
1050
- <h3>Quick Presets</h3>
1051
- <TechnicalTooltip text="Pre-configured settings to demonstrate different privacy-utility trade-offs." />
1052
- </div>
1053
-
1054
- <div className="preset-buttons">
1055
- <button
1056
- className="preset-button high-privacy"
1057
- disabled={disabled}
1058
- onClick={() => onConfigChange({
1059
- dpParams: {
1060
- clipNorm: 1.0,
1061
- noiseMultiplier: 1.5,
1062
- batchSize: 256,
1063
- learningRate: 0.005,
1064
- epochs: 10
1065
- }
1066
- })}
1067
- >
1068
- <span className="preset-icon">🔒</span>
1069
- <span className="preset-name">High Privacy</span>
1070
- <span className="preset-description">ε ≈ 1.2</span>
1071
- </button>
1072
-
1073
- <button
1074
- className="preset-button balanced"
1075
- disabled={disabled}
1076
- onClick={() => onConfigChange({
1077
- dpParams: {
1078
- clipNorm: 1.0,
1079
- noiseMultiplier: 1.0,
1080
- batchSize: 128,
1081
- learningRate: 0.01,
1082
- epochs: 8
1083
- }
1084
- })}
1085
- >
1086
- <span className="preset-icon">⚖️</span>
1087
- <span className="preset-name">Balanced</span>
1088
- <span className="preset-description">ε ≈ 3.0</span>
1089
- </button>
1090
-
1091
- <button
1092
- className="preset-button high-utility"
1093
- disabled={disabled}
1094
- onClick={() => onConfigChange({
1095
- dpParams: {
1096
- clipNorm: 1.5,
1097
- noiseMultiplier: 0.5,
1098
- batchSize: 64,
1099
- learningRate: 0.02,
1100
- epochs: 5
1101
- }
1102
- })}
1103
- >
1104
- <span className="preset-icon">📈</span>
1105
- <span className="preset-name">High Utility</span>
1106
- <span className="preset-description">ε ≈ 8.0</span>
1107
- </button>
1108
- </div>
1109
- </div>
1110
- </div>
1111
- );
1112
- };
1113
-
1114
- // Parameter Control Panel
1115
- const ParameterControlPanel = ({ dpParams, onParamChange, expectedPrivacyBudget, disabled }) => {
1116
- const parameterDescriptions = {
1117
- clipNorm: "Limits how much any single training example can affect the model update. Smaller values provide stronger privacy but can slow learning.",
1118
- noiseMultiplier: "Controls how much noise is added to protect privacy. Higher values increase privacy but may reduce accuracy.",
1119
- batchSize: "Number of examples processed in each training step. Affects both privacy accounting and training stability.",
1120
- learningRate: "Controls how quickly model parameters update. For DP-SGD, often needs to be smaller than standard SGD.",
1121
- epochs: "Number of complete passes through the dataset. More epochs improves learning but increases privacy budget consumption."
1122
- };
1123
-
1124
- const handleSliderChange = (paramName, event) => {
1125
- const value = parseFloat(event.target.value);
1126
- onParamChange(paramName, value);
1127
- };
1128
-
1129
- const renderSlider = (paramName, min, max, step, formatter = (v) => v) => {
1130
- return (
1131
- <div className="parameter-control" key={paramName}>
1132
- <div className="parameter-header">
1133
- <label htmlFor={`param-${paramName}`} className="parameter-label">
1134
- {getParameterLabel(paramName)}
1135
- </label>
1136
- <TechnicalTooltip text={parameterDescriptions[paramName]} />
1137
- </div>
1138
-
1139
- <div className="slider-container">
1140
- <input
1141
- id={`param-${paramName}`}
1142
- type="range"
1143
- min={min}
1144
- max={max}
1145
- step={step}
1146
- value={dpParams[paramName]}
1147
- onChange={(e) => handleSliderChange(paramName, e)}
1148
- disabled={disabled}
1149
- className="parameter-slider"
1150
- />
1151
- <span className="parameter-value">{formatter(dpParams[paramName])}</span>
1152
- </div>
1153
- </div>
1154
- );
1155
- };
1156
-
1157
- const getParameterLabel = (paramName) => {
1158
- switch (paramName) {
1159
- case 'clipNorm':
1160
- return 'Clipping Norm (C)';
1161
- case 'noiseMultiplier':
1162
- return 'Noise Multiplier (σ)';
1163
- case 'batchSize':
1164
- return 'Batch Size';
1165
- case 'learningRate':
1166
- return 'Learning Rate (η)';
1167
- case 'epochs':
1168
- return 'Epochs';
1169
- default:
1170
- return paramName;
1171
- }
1172
- };
1173
-
1174
- // Helper to classify privacy budget for styling
1175
- const getBudgetClass = (epsilon) => {
1176
- if (epsilon <= 1) return 'excellent';
1177
- if (epsilon <= 3) return 'good';
1178
- if (epsilon <= 6) return 'moderate';
1179
- return 'weak';
1180
- };
1181
-
1182
- return (
1183
- <div className="parameter-control-panel">
1184
- <h2 className="panel-title">DP-SGD Parameters</h2>
1185
-
1186
- {renderSlider('clipNorm', 0.1, 5.0, 0.1, (v) => v.toFixed(1))}
1187
- {renderSlider('noiseMultiplier', 0.1, 5.0, 0.1, (v) => v.toFixed(1))}
1188
- {renderSlider('batchSize', 16, 512, 16, (v) => v)}
1189
- {renderSlider('learningRate', 0.001, 0.1, 0.001, (v) => v.toFixed(3))}
1190
- {renderSlider('epochs', 1, 20, 1, (v) => v)}
1191
-
1192
- <div className="privacy-budget-estimate">
1193
- <div className="budget-header">
1194
- <h3>Estimated Privacy Budget (ε)</h3>
1195
- <TechnicalTooltip text="This is the estimated privacy loss from training with these parameters. Lower ε means stronger privacy guarantees." />
1196
- </div>
1197
-
1198
- <div className="budget-display">
1199
- <div className="budget-value">{expectedPrivacyBudget.toFixed(2)}</div>
1200
- <div className="budget-indicator">
1201
- <div className="budget-bar">
1202
- <div
1203
- className={`budget-fill ${getBudgetClass(expectedPrivacyBudget)}`}
1204
- style={{ width: `${Math.min(expectedPrivacyBudget / 10 * 100, 100)}%` }}
1205
- ></div>
1206
- </div>
1207
- <div className="budget-scale">
1208
- <span>Stronger Privacy</span>
1209
- <span>Weaker Privacy</span>
1210
- </div>
1211
- </div>
1212
- </div>
1213
- </div>
1214
- </div>
1215
- );
1216
- };
1217
-
1218
- // Training Visualizer with Recharts
1219
- const TrainingVisualizer = ({ progress, isTraining, config }) => {
1220
- const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } = Recharts;
1221
- const [activeTab, setActiveTab] = React.useState('training');
1222
- const gradientCanvasRef = React.useRef(null);
1223
-
1224
- // Prepare chart data
1225
- const chartData = React.useMemo(() => {
1226
- if (!progress.accuracy || !progress.loss) return [];
1227
-
1228
- return progress.accuracy.map((acc, idx) => ({
1229
- epoch: idx + 1,
1230
- accuracy: acc * 100, // Convert to percentage
1231
- loss: progress.loss[idx] || 0,
1232
- privacyBudget: progress.privacyBudgetUsed[idx] || 0
1233
- }));
1234
- }, [progress]);
1235
-
1236
- // Draw gradient clipping visualization in canvas
1237
- React.useEffect(() => {
1238
- if (activeTab === 'gradients' && gradientCanvasRef.current) {
1239
- const canvas = gradientCanvasRef.current;
1240
- const ctx = canvas.getContext('2d');
1241
-
1242
- // Clear canvas
1243
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1244
-
1245
- // Mock drawing gradient distribution
1246
- drawGradientDistribution(ctx, canvas.width, canvas.height, config.dpParams.clipNorm);
1247
- }
1248
- }, [activeTab, config.dpParams.clipNorm]);
1249
-
1250
- // Helper to draw gradient clipping visualization
1251
- const drawGradientDistribution = (ctx, width, height, clipNorm) => {
1252
- // Setup
1253
- const padding = { top: 20, right: 30, bottom: 40, left: 60 };
1254
- const chartWidth = width - padding.left - padding.right;
1255
- const chartHeight = height - padding.top - padding.bottom;
1256
-
1257
- // Draw axes
1258
- ctx.strokeStyle = '#ccc';
1259
- ctx.lineWidth = 1;
1260
-
1261
- // Y-axis
1262
- ctx.beginPath();
1263
- ctx.moveTo(padding.left, padding.top);
1264
- ctx.lineTo(padding.left, height - padding.bottom);
1265
- ctx.stroke();
1266
-
1267
- // X-axis
1268
- ctx.beginPath();
1269
- ctx.moveTo(padding.left, height - padding.bottom);
1270
- ctx.lineTo(width - padding.right, height - padding.bottom);
1271
- ctx.stroke();
1272
-
1273
- // Draw distribution curve - before clipping
1274
- ctx.beginPath();
1275
- ctx.moveTo(padding.left, height - padding.bottom);
1276
-
1277
- // Lognormal-like curve
1278
- for (let i = 0; i < chartWidth; i++) {
1279
- const x = padding.left + i;
1280
- const xValue = (i / chartWidth) * 10; // Scale to 0-10 range
1281
-
1282
- // Lognormal-ish function
1283
- let y = Math.exp(-Math.pow(Math.log(xValue + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5);
1284
- y = height - padding.bottom - y * chartHeight * 0.8;
1285
-
1286
- if (i === 0) {
1287
- ctx.moveTo(x, y);
1288
- } else {
1289
- ctx.lineTo(x, y);
1290
- }
1291
- }
1292
-
1293
- ctx.strokeStyle = '#ff9800';
1294
- ctx.lineWidth = 3;
1295
- ctx.stroke();
1296
-
1297
- // Draw distribution curve - after clipping
1298
- ctx.beginPath();
1299
- ctx.moveTo(padding.left, height - padding.bottom);
1300
-
1301
- // Calculate clipping threshold position
1302
- const clipX = padding.left + (clipNorm / 10) * chartWidth;
1303
-
1304
- // Draw up to clipping threshold
1305
- for (let i = 0; i < chartWidth; i++) {
1306
- const x = padding.left + i;
1307
- const xValue = (i / chartWidth) * 10;
1308
-
1309
- // If beyond clipping threshold, flatline at the threshold value
1310
- if (x > clipX) break;
1311
-
1312
- // Same curve as before
1313
- let y = Math.exp(-Math.pow(Math.log(xValue + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5);
1314
- y = height - padding.bottom - y * chartHeight * 0.8;
1315
-
1316
- if (i === 0) {
1317
- ctx.moveTo(x, y);
1318
- } else {
1319
- ctx.lineTo(x, y);
1320
- }
1321
- }
1322
-
1323
- // Calculate y at clipping point
1324
- const clipY = height - padding.bottom - Math.exp(-Math.pow(Math.log(clipNorm + 0.1) - Math.log(clipNorm * 0.7), 2) / 0.5) * chartHeight * 0.8;
1325
-
1326
- // Draw the rest of the clipped distribution
1327
- for (let i = Math.floor(clipX - padding.left); i < chartWidth; i++) {
1328
- const x = padding.left + i;
1329
-
1330
- // Values beyond are clipped
1331
- if (i === Math.floor(clipX - padding.left)) {
1332
- ctx.lineTo(clipX, clipY);
1333
- } else {
1334
- // Add spikes at the clipping threshold to show accumulation
1335
- const spike = Math.sin(i * 0.5) * 5;
1336
- ctx.lineTo(x, clipY - spike);
1337
- }
1338
- }
1339
-
1340
- ctx.strokeStyle = '#4caf50';
1341
- ctx.lineWidth = 3;
1342
- ctx.stroke();
1343
-
1344
- // Draw clipping threshold line
1345
- ctx.beginPath();
1346
- ctx.moveTo(clipX, padding.top);
1347
- ctx.lineTo(clipX, height - padding.bottom);
1348
- ctx.strokeStyle = '#f44336';
1349
- ctx.lineWidth = 2;
1350
- ctx.setLineDash([5, 3]);
1351
- ctx.stroke();
1352
- ctx.setLineDash([]);
1353
-
1354
- // Draw labels
1355
- ctx.fillStyle = '#333';
1356
- ctx.font = '12px Arial';
1357
- ctx.textAlign = 'center';
1358
-
1359
- // X-axis label
1360
- ctx.fillText('Gradient L2 Norm', width / 2, height - 5);
1361
-
1362
- // Clipping threshold label
1363
- ctx.fillText(`Clipping Threshold (C = ${clipNorm})`, clipX, padding.top - 5);
1364
-
1365
- // Legend
1366
- ctx.textAlign = 'left';
1367
- ctx.fillStyle = '#ff9800';
1368
- ctx.fillRect(padding.left, padding.top, 10, 10);
1369
- ctx.fillStyle = '#333';
1370
- ctx.fillText('Original Gradients', padding.left + 15, padding.top + 9);
1371
-
1372
- ctx.fillStyle = '#4caf50';
1373
- ctx.fillRect(padding.left, padding.top + 20, 10, 10);
1374
- ctx.fillStyle = '#333';
1375
- ctx.fillText('Clipped Gradients', padding.left + 15, padding.top + 29);
1376
- };
1377
-
1378
- return (
1379
- <div className="training-visualizer">
1380
- <div className="visualizer-header">
1381
- <h2 className="visualizer-title">Training Progress</h2>
1382
- <div className="visualizer-tabs">
1383
- <button
1384
- className={`tab-button ${activeTab === 'training' ? 'active' : ''}`}
1385
- onClick={() => setActiveTab('training')}
1386
- >
1387
- Training Metrics
1388
- </button>
1389
- <button
1390
- className={`tab-button ${activeTab === 'gradients' ? 'active' : ''}`}
1391
- onClick={() => setActiveTab('gradients')}
1392
- >
1393
- Gradient Clipping
1394
- </button>
1395
- <button
1396
- className={`tab-button ${activeTab === 'privacy' ? 'active' : ''}`}
1397
- onClick={() => setActiveTab('privacy')}
1398
- >
1399
- Privacy Budget
1400
- </button>
1401
- </div>
1402
- </div>
1403
-
1404
- <div className="visualizer-content">
1405
- {activeTab === 'training' && (
1406
- <div className="metrics-chart">
1407
- <ResponsiveContainer width="100%" height={300}>
1408
- <LineChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
1409
- <CartesianGrid strokeDasharray="3 3" />
1410
- <XAxis dataKey="epoch" label={{ value: 'Epoch', position: 'insideBottomRight', offset: -5 }} />
1411
- <YAxis yAxisId="left" label={{ value: 'Accuracy (%)', angle: -90, position: 'insideLeft' }} />
1412
- <YAxis yAxisId="right" orientation="right" label={{ value: 'Loss', angle: 90, position: 'insideRight' }} />
1413
- <Tooltip />
1414
- <Legend />
1415
- <Line yAxisId="left" type="monotone" dataKey="accuracy" stroke="#4caf50" name="Accuracy" />
1416
- <Line yAxisId="right" type="monotone" dataKey="loss" stroke="#f44336" name="Loss" />
1417
- </LineChart>
1418
- </ResponsiveContainer>
1419
-
1420
- {isTraining && (
1421
- <div className="training-status">
1422
- <div className="status-badge">
1423
- <span className="pulse"></span>
1424
- <span className="status-text">Training in progress</span>
1425
- </div>
1426
- <div className="current-epoch">
1427
- Epoch: {progress.currentEpoch + 1} / {config.dpParams.epochs}
1428
- </div>
1429
- </div>
1430
- )}
1431
- </div>
1432
- )}
1433
-
1434
- {activeTab === 'gradients' && (
1435
- <div className="gradients-visualization">
1436
- <div className="explanation-block">
1437
- <h3>Gradient Clipping Visualization</h3>
1438
- <p>
1439
- The chart below shows a distribution of gradient norms before and after clipping.
1440
- The vertical red line indicates the clipping threshold (C = {config.dpParams.clipNorm}).
1441
- <TechnicalTooltip text="Clipping ensures no single example has too much influence on model updates, which is essential for differential privacy." />
1442
- </p>
1443
- </div>
1444
-
1445
- <div className="gradient-canvas-container">
1446
- <canvas
1447
- ref={gradientCanvasRef}
1448
- width={600}
1449
- height={300}
1450
- className="gradient-canvas"
1451
- />
1452
- </div>
1453
- </div>
1454
- )}
1455
-
1456
- {activeTab === 'privacy' && (
1457
- <div className="privacy-visualization">
1458
- <div className="explanation-block">
1459
- <h3>Privacy Budget Consumption</h3>
1460
- <p>
1461
- This chart shows how the privacy budget (ε) accumulates during training.
1462
- <TechnicalTooltip text="In differential privacy, we track the 'privacy budget' (ε) which represents the amount of privacy loss. Lower values mean stronger privacy guarantees." />
1463
- </p>
1464
- </div>
1465
-
1466
- <ResponsiveContainer width="100%" height={300}>
1467
- <LineChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
1468
- <CartesianGrid strokeDasharray="3 3" />
1469
- <XAxis dataKey="epoch" label={{ value: 'Epoch', position: 'insideBottomRight', offset: -5 }} />
1470
- <YAxis label={{ value: 'Privacy Budget (ε)', angle: -90, position: 'insideLeft' }} />
1471
- <Tooltip />
1472
- <Legend />
1473
- <Line type="monotone" dataKey="privacyBudget" stroke="#3f51b5" name="Privacy Budget (ε)" />
1474
- </LineChart>
1475
- </ResponsiveContainer>
1476
- </div>
1477
- )}
1478
- </div>
1479
- </div>
1480
- );
1481
- };
1482
-
1483
- // Results Panel
1484
- const ResultsPanel = ({ results, config }) => {
1485
- const [showFinalMetrics, setShowFinalMetrics] = React.useState(true);
1486
-
1487
- // Format metrics for display
1488
- const formatMetric = (value, precision = 2) => {
1489
- return typeof value === 'number' ? value.toFixed(precision) : 'N/A';
1490
- };
1491
-
1492
- // Get privacy class for styling
1493
- const getPrivacyClass = (epsilon) => {
1494
- if (epsilon <= 1) return 'excellent';
1495
- if (epsilon <= 3) return 'good';
1496
- if (epsilon <= 6) return 'moderate';
1497
- return 'weak';
1498
- };
1499
-
1500
- // Generate explanation about privacy-utility tradeoff
1501
- const getTradeoffExplanation = (accuracy, priv<!DOCTYPE html>
1502
- <html lang="en">
1503
- <head>
1504
- <meta charset="UTF-8">
1505
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1506
- <title>DP-SGD Explorer - Interactive Educational Tool</title>
1507
-
1508
- <!-- Load React -->
1509
- <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
1510
- <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
1511
-
1512
- <!-- Load Babel for JSX -->
1513
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
1514
-
1515
- <!-- Load TensorFlow.js -->
1516
- <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
1517
-
1518
- <!-- Load Recharts for visualization -->
1519
- <script src="https://unpkg.com/[email protected]/umd/Recharts.min.js"></script>
1520
-
1521
- <style>
1522
- /* Base Styles */
1523
- :root {
1524
- /* Color Palette */
1525
- --primary-color: #3f51b5;
1526
- --primary-light: #757de8;
1527
- --primary-dark: #002984;
1528
- --secondary-color: #4caf50;
1529
- --secondary-light: #80e27e;
1530
- --secondary-dark: #087f23;
1531
- --accent-color: #ff9800;
1532
- --error-color: #f44336;
1533
- --text-primary: #333333;
1534
- --text-secondary: #666666;
1535
- --text-light: #ffffff;
1536
- --background-light: #ffffff;
1537
- --background-off: #f5f7fa;
1538
- --background-dark: #e0e0e0;
1539
- --border-color: #dddddd;
1540
-
1541
- /* Typography */
1542
- --font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, sans-serif;
1543
- --font-size-small: 0.875rem;
1544
- --font-size-medium: 1rem;
1545
- --font-size-large: 1.125rem;
1546
- --font-size-xlarge: 1.5rem;
1547
- --font-size-xxlarge: 2rem;
1548
-
1549
- /* Spacing */
1550
- --spacing-xs: 0.25rem;
1551
- --spacing-sm: 0.5rem;
1552
- --spacing-md: 1rem;
1553
- --spacing-lg: 1.5rem;
1554
- --spacing-xl: 2rem;
1555
- --spacing-xxl: 3rem;
1556
-
1557
- /* Shadows */
1558
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
1559
- --shadow-md: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
1560
- --shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
1561
-
1562
- /* Border Radius */
1563
- --border-radius-sm: 0.25rem;
1564
- --border-radius-md: 0.5rem;
1565
- --border-radius-lg: 1rem;
1566
-
1567
- /* Transitions */
1568
- --transition-fast: 0.2s ease;
1569
- --transition-normal: 0.3s ease;
1570
- --transition-slow: 0.5s ease;
1571
- }
1572
-
1573
- * {
1574
- box-sizing: border-box;
1575
- margin: 0;
1576
- padding: 0;
1577
- }
1578
-
1579
- html, body {
1580
- font-family: var(--font-family);
1581
- font-size: var(--font-size-medium);
1582
- color: var(--text-primary);
1583
- background-color: var(--background-off);
1584
- line-height: 1.5;
1585
- }
1586
-
1587
- .app-container {
1588
- display: flex;
1589
- flex-direction: column;
1590
- min-height: 100vh;
1591
- }
1592
-
1593
- .main-content {
1594
- flex: 1;
1595
- padding: var(--spacing-lg);
1596
- max-width: 1400px;
1597
- margin: 0 auto;
1598
- width: 100%;
1599
- }
1600
-
1601
- h1, h2, h3, h4, h5, h6 {
1602
- margin-bottom: var(--spacing-md);
1603
- font-weight: 500;
1604
- }
1605
-
1606
- a {
1607
- color: var(--primary-color);
1608
- text-decoration: none;
1609
- transition: color var(--transition-fast);
1610
- }
1611
-
1612
- a:hover {
1613
- color: var(--primary-light);
1614
- }
1615
-
1616
- button {
1617
- cursor: pointer;
1618
- font-family: var(--font-family);
1619
- font-size: var(--font-size-medium);
1620
- border: none;
1621
- background: none;
1622
- }
1623
-
1624
- button:disabled {
1625
- cursor: not-allowed;
1626
- opacity: 0.6;
1627
- }
1628
-
1629
- /* Header & Navigation */
1630
- .main-header {
1631
- background-color: var(--primary-color);
1632
- color: var(--text-light);
1633
- padding: var(--spacing-md) var(--spacing-lg);
1634
- box-shadow: var(--shadow-md);
1635
- position: sticky;
1636
- top: 0;
1637
- z-index: 100;
1638
- }
1639
-
1640
- .header-container {
1641
- display: flex;
1642
- justify-content: space-between;
1643
- align-items: center;
1644
- max-width: 1400px;
1645
- margin: 0 auto;
1646
- }
1647
-
1648
- .logo-container {
1649
- display: flex;
1650
- flex-direction: column;
1651
- }
1652
-
1653
- .logo {
1654
- font-size: var(--font-size-xlarge);
1655
- font-weight: 700;
1656
- color: var(--text-light);
1657
- text-decoration: none;
1658
- }
1659
-
1660
- .tagline {
1661
- font-size: var(--font-size-small);
1662
- opacity: 0.8;
1663
- }
1664
-
1665
- .main-nav .nav-list {
1666
- display: flex;
1667
- list-style: none;
1668
- gap: var(--spacing-lg);
1669
- }
1670
-
1671
- .nav-link {
1672
- color: var(--text-light);
1673
- opacity: 0.9;
1674
- display: flex;
1675
- align-items: center;
1676
- gap: var(--spacing-xs);
1677
- padding: var(--spacing-sm) var(--spacing-md);
1678
- border-radius: var(--border-radius-sm);
1679
- transition: all var(--transition-fast);
1680
- cursor: pointer;
1681
- }
1682
-
1683
- .nav-link:hover {
1684
- opacity: 1;
1685
- background-color: rgba(255, 255, 255, 0.1);
1686
- }
1687
-
1688
- .nav-link.active {
1689
- background-color: rgba(255, 255, 255, 0.2);
1690
- opacity: 1;
1691
- }
1692
-
1693
- .nav-icon {
1694
- font-size: var(--font-size-large);
1695
- }
1696
-
1697
- /* User selector */
1698
- .user-role-selector {
1699
- position: relative;
1700
- }
1701
-
1702
- .role-button {
1703
- display: flex;
1704
- align-items: center;
1705
- gap: var(--spacing-sm);
1706
- background-color: rgba(255, 255, 255, 0.2);
1707
- color: var(--text-light);
1708
- padding: var(--spacing-sm) var(--spacing-md);
1709
- border-radius: var(--border-radius-sm);
1710
- transition: all var(--transition-fast);
1711
- }
1712
-
1713
- .role-button:hover {
1714
- background-color: rgba(255, 255, 255, 0.3);
1715
- }
1716
-
1717
- .role-dropdown {
1718
- position: absolute;
1719
- top: 100%;
1720
- right: 0;
1721
- margin-top: var(--spacing-xs);
1722
- background-color: var(--background-light);
1723
- box-shadow: var(--shadow-md);
1724
- border-radius: var(--border-radius-sm);
1725
- min-width: 200px;
1726
- overflow: hidden;
1727
- z-index: 1000;
1728
- }
1729
-
1730
- .role-dropdown ul {
1731
- list-style: none;
1732
- }
1733
-
1734
- .role-dropdown button {
1735
- width: 100%;
1736
- text-align: left;
1737
- padding: var(--spacing-md);
1738
- display: flex;
1739
- justify-content: space-between;
1740
- align-items: center;
1741
- transition: all var(--transition-fast);
1742
- }
1743
-
1744
- .role-dropdown button:hover {
1745
- background-color: var(--background-off);
1746
- }
1747
-
1748
- .role-dropdown button.active {
1749
- background-color: var(--primary-light);
1750
- color: var(--text-light);
1751
- }
1752
-
1753
- /* Tooltips */
1754
- .tooltip-container {
1755
- position: relative;
1756
- display: inline-flex;
1757
- align-items: center;
1758
- margin-left: var(--spacing-xs);
1759
- }
1760
-
1761
- .tooltip-icon {
1762
- cursor: help;
1763
- display: flex;
1764
- align-items: center;
1765
- justify-content: center;
1766
- width: 16px;
1767
- height: 16px;
1768
- border-radius: 50%;
1769
- background-color: var(--primary-light);
1770
- color: var(--text-light);
1771
- font-size: 11px;
1772
- }
1773
-
1774
- .tooltip-content {
1775
- position: absolute;
1776
- top: 100%;
1777
- left: 50%;
1778
- transform: translateX(-50%);
1779
- margin-top: var(--spacing-xs);
1780
- padding: var(--spacing-sm);
1781
- background-color: var(--text-primary);
1782
- color: var(--text-light);
1783
- font-size: var(--font-size-small);
1784
- border-radius: var(--border-radius-sm);
1785
- box-shadow: var(--shadow-md);
1786
- width: max-content;
1787
- max-width: 250px;
1788
- z-index: 1000;
1789
- }
1790
-
1791
- .tooltip-content::before {
1792
- content: '';
1793
- position: absolute;
1794
- bottom: 100%;
1795
- left: 50%;
1796
- transform: translateX(-50%);
1797
- border: 6px solid transparent;
1798
- border-bottom-color: var(--text-primary);
1799
- }
1800
-
1801
- .tooltip-content.wide {
1802
- max-width: 300px;
1803
- }
1804
-
1805
- /* Section Titles */
1806
- .section-title {
1807
- font-size: var(--font-size-xxlarge);
1808
- color: var(--primary-dark);
1809
- margin-bottom: var(--spacing-xl);
1810
- position: relative;
1811
- display: inline-block;
1812
- }
1813
-
1814
- .section-title::after {
1815
- content: '';
1816
- position: absolute;
1817
- bottom: -8px;
1818
- left: 0;
1819
- width: 60px;
1820
- height: 4px;
1821
- background-color: var(--primary-color);
1822
- border-radius: 2px;
1823
- }
1824
-
1825
- .panel-title {
1826
- font-size: var(--font-size-large);
1827
- margin-bottom: var(--spacing-md);
1828
- color: var(--primary-dark);
1829
- }
1830
-
1831
- /* Hands-On Lab Layout */
1832
- .hands-on-lab {
1833
- margin-top: var(--spacing-md);
1834
- }
1835
-
1836
- .lab-container {
1837
- display: grid;
1838
- grid-template-columns: 300px 1fr;
1839
- gap: var(--spacing-lg);
1840
- }
1841
-
1842
- .lab-sidebar {
1843
- display: flex;
1844
- flex-direction: column;
1845
- gap: var(--spacing-lg);
1846
- }
1847
-
1848
- .lab-main {
1849
- display: flex;
1850
- flex-direction: column;
1851
- gap: var(--spacing-lg);
1852
- }
1853
-
1854
- .visualizer-container,
1855
- .results-container {
1856
- background-color: var(--background-light);
1857
- border-radius: var(--border-radius-md);
1858
- padding: var(--spacing-lg);
1859
- box-shadow: var(--shadow-sm);
1860
- }
1861
-
1862
- /* Model Configuration Panel */
1863
- .model-configuration-panel,
1864
- .parameter-control-panel {
1865
- background-color: var(--background-light);
1866
- border-radius: var(--border-radius-md);
1867
- padding: var(--spacing-lg);
1868
- box-shadow: var(--shadow-sm);
1869
- }
1870
-
1871
- .config-section {
1872
- margin-bottom: var(--spacing-lg);
1873
- }
1874
-
1875
- .section-header {
1876
- display: flex;
1877
- align-items: center;
1878
- margin-bottom: var(--spacing-sm);
1879
- }
1880
-
1881
- .section-header h3 {
1882
- font-size: var(--font-size-medium);
1883
- margin-bottom: 0;
1884
- }
1885
-
1886
- .config-select {
1887
- width: 100%;
1888
- padding: var(--spacing-sm);
1889
- border-radius: var(--border-radius-sm);
1890
- border: 1px solid var(--border-color);
1891
- font-family: var(--font-family);
1892
- font-size: var(--font-size-medium);
1893
- margin-bottom: var(--spacing-sm);
1894
- }
1895
-
1896
- .dataset-info, .model-info {
1897
- background-color: var(--background-off);
1898
- border-radius: var(--border-radius-sm);
1899
- padding: var(--spacing-sm);
1900
- font-size: var(--font-size-small);
1901
- }
1902
-
1903
- .info-item {
1904
- display: flex;
1905
- margin-bottom: var(--spacing-xs);
1906
- gap: var(--spacing-sm);
1907
- }
1908
-
1909
- .info-label {
1910
- font-weight: 500;
1911
- color: var(--text-secondary);
1912
- min-width: 100px;
1913
- }
1914
-
1915
- .complexity-badge {
1916
- display: inline-block;
1917
- padding: 2px 8px;
1918
- border-radius: var(--border-radius-sm);
1919
- font-size: var(--font-size-small);
1920
- font-weight: 500;
1921
- }
1922
-
1923
- .complexity-badge.low {
1924
- background-color: #81c784;
1925
- color: #1b5e20;
1926
- }
1927
-
1928
- .complexity-badge.medium {
1929
- background-color: #fff176;
1930
- color: #f57f17;
1931
- }
1932
-
1933
- .complexity-badge.high {
1934
- background-color: #ef9a9a;
1935
- color: #b71c1c;
1936
- }
1937
-
1938
- .preset-section {
1939
- margin-bottom: 0;
1940
- }
1941
-
1942
- .preset-buttons {
1943
- display: grid;
1944
- grid-template-columns: repeat(3, 1fr);
1945
- gap: var(--spacing-sm);
1946
- }
1947
-
1948
- .preset-button {
1949
- display: flex;
1950
- flex-direction: column;
1951
- align-items: center;
1952
- padding: var(--spacing-sm);
1953
- border-radius: var(--border-radius-sm);
1954
- background-color: var(--background-off);
1955
- transition: all var(--transition-fast);
1956
- text-align: center;
1957
- }
1958
-
1959
- .preset-button:hover:not(:disabled) {
1960
- box-shadow: var(--shadow-sm);
1961
- }
1962
-
1963
- .preset-button .preset-icon {
1964
- font-size: var(--font-size-xlarge);
1965
- margin-bottom: var(--spacing-xs);
1966
- }
1967
-
1968
- .preset-button .preset-name {
1969
- font-weight: 500;
1970
- margin-bottom: var(--spacing-xs);
1971
- }
1972
-
1973
- .preset-button .preset-description {
1974
- font-size: var(--font-size-small);
1975
- color: var(--text-secondary);
1976
- }
1977
-
1978
- .preset-button.high-privacy {
1979
- background-color: #e3f2fd;
1980
- }
1981
-
1982
- .preset-button.balanced {
1983
- background-color: #f1f8e9;
1984
- }
1985
-
1986
- .preset-button.high-utility {
1987
- background-color: #fff8e1;
1988
- }
1989
-
1990
- /* Parameter Control Panel */
1991
- .parameter-control {
1992
- margin-bottom: var(--spacing-md);
1993
- }
1994
-
1995
- .parameter-header {
1996
- display: flex;
1997
- align-items: center;
1998
- margin-bottom: var(--spacing-xs);
1999
- }
2000
-
2001
- .parameter-label {
2002
- font-weight: 500;
2003
- }
2004
-
2005
- .slider-container {
2006
- display: flex;
2007
- align-items: center;
2008
- gap: var(--spacing-md);
2009
- }
2010
-
2011
- .parameter-slider {
2012
- flex: 1;
2013
- height: 4px;
2014
- -webkit-appearance: none;
2015
- appearance: none;
2016
- background: var(--background-dark);
2017
- outline: none;
2018
- border-radius: 2px;
2019
- }
2020
-
2021
- .parameter-slider::-webkit-slider-thumb {
2022
- -webkit-appearance: none;
2023
- appearance: none;
2024
- width: 16px;
2025
- height: 16px;
2026
- border-radius: 50%;
2027
- background: var(--primary-color);
2028
- cursor: pointer;
2029
- box-shadow: var(--shadow-sm);
2030
- }
2031
-
2032
- .parameter-slider::-moz-range-thumb {
2033
- width: 16px;
2034
- height: 16px;
2035
- border-radius: 50%;
2036
- background: var(--primary-color);
2037
- cursor: pointer;
2038
- box-shadow: var(--shadow-sm);
2039
- border: none;
2040
- }
2041
-
2042
- .parameter-value {
2043
- min-width: 40px;
2044
- font-weight: 500;
2045
- }
2046
-
2047
- .privacy-budget-estimate {
2048
- margin-top: var(--spacing-lg);
2049
- background-color: var(--background-off);
2050
- border-radius: var(--border-radius-sm);
2051
- padding: var(--spacing-md);
2052
- }
2053
-
2054
- .budget-header {
2055
- display: flex;
2056
- align-items: center;
2057
- margin-bottom: var(--spacing-sm);
2058
- }
2059
-
2060
- .budget-header h3 {
2061
- font-size: var(--font-size-medium);
2062
- margin-bottom: 0;
2063
- }
2064
-
2065
- .budget-display {
2066
- display: flex;
2067
- align-items: center;
2068
- gap: var(--spacing-md);
2069
- }
2070
-
2071
- .budget-value {
2072
- font-size: var(--font-size-xlarge);
2073
- font-weight: 500;
2074
- min-width: 60px;
2075
- }
2076
-
2077
- .budget-indicator {
2078
- flex: 1;
2079
- }
2080
-
2081
- .budget-bar {
2082
- height: 8px;
2083
- background-color: var(--background-dark);
2084
- border-radius: 4px;
2085
- position: relative;
2086
- margin-bottom: var(--spacing-xs);
2087
- }
2088
-
2089
- .budget-fill {
2090
- height: 100%;
2091
- border-radius: 4px;
2092
- transition: width var(--transition-normal);
2093
- }
2094
-
2095
- .budget-fill.excellent {
2096
- background-color: var(--secondary-color);
2097
- }
2098
-
2099
- .budget-fill.good {
2100
- background-color: var(--accent-color);
2101
- }
2102
-
2103
- .budget-fill.moderate {
2104
- background-color: #ff9800;
2105
- }
2106
-
2107
- .budget-fill.weak {
2108
- background-color: var(--error-color);
2109
- }
2110
-
2111
- .budget-scale {
2112
- display: flex;
2113
- justify-content: space-between;
2114
- font-size: var(--font-size-small);
2115
- color: var(--text-secondary);
2116
- }
2117
-
2118
- .control-buttons {
2119
- display: flex;
2120
- gap: var(--spacing-md);
2121
- margin-top: var(--spacing-md);
2122
- }
2123
-
2124
- .primary-button, .secondary-button {
2125
- padding: var(--spacing-md) var(--spacing-lg);
2126
- border-radius: var(--border-radius-sm);
2127
- font-weight: 500;
2128
- text-align: center;
2129
- transition: all var(--transition-fast);
2130
- flex: 1;
2131
- }
2132
-
2133
- .primary-button {
2134
- background-color: var(--primary-color);
2135
- color: var(--text-light);
2136
- }
2137
-
2138
- .primary-button:hover:not(:disabled) {
2139
- background-color: var(--primary-dark);
2140
- }
2141
-
2142
- .primary-button.running {
2143
- background-color: var(--error-color);
2144
- }
2145
-
2146
- .secondary-button {
2147
- background-color: var(--background-off);
2148
- color: var(--primary-color);
2149
- border: 1px solid var(--primary-color);
2150
- }
2151
-
2152
- .secondary-button:hover:not(:disabled) {
2153
- background-color: var(--primary-color);
2154
- color: var(--text-light);
2155
- }
2156
-
2157
- /* Training Visualizer */
2158
- .training-visualizer {
2159
- margin-bottom: var(--spacing-lg);
2160
- }
2161
-
2162
- .visualizer-header {
2163
- display: flex;
2164
- justify-content: space-between;
2165
- align-items: center;
2166
- margin-bottom: var(--spacing-md);
2167
- }
2168
-
2169
- .visualizer-tabs {
2170
- display: flex;
2171
- gap: var(--spacing-xs);
2172
- }
2173
-
2174
- .tab-button {
2175
- padding: var(--spacing-sm) var(--spacing-md);
2176
- border-radius: var(--border-radius-sm);
2177
- font-size: var(--font-size-small);
2178
- transition: all var(--transition-fast);
2179
- }
2180
-
2181
- .tab-button:hover {
2182
- background-color: var(--background-off);
2183
- }
2184
-
2185
- .tab-button.active {
2186
- background-color: var(--primary-color);
2187
- color: var(--text-light);
2188
- }
2189
-
2190
- .training-status {
2191
- display: flex;
2192
- justify-content: space-between;
2193
- align-items: center;
2194
- margin-top: var(--spacing-md);
2195
- padding: var(--spacing-sm) var(--spacing-md);
2196
- background-color: var(--background-off);
2197
- border-radius: var(--border-radius-sm);
2198
- }
2199
-
2200
- .status-badge {
2201
- display: flex;
2202
- align-items: center;
2203
- gap: var(--spacing-sm);
2204
- }
2205
-
2206
- .pulse {
2207
- display: inline-block;
2208
- width: 10px;
2209
- height: 10px;
2210
- border-radius: 50%;
2211
- background-color: #4caf50;
2212
- animation: pulse 1.5s infinite;
2213
- }
2214
-
2215
- @keyframes pulse {
2216
- 0% {
2217
- box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);
2218
- }
2219
- 70% {
2220
- box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
2221
- }
2222
- 100% {
2223
- box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
2224
- }
2225
- }
2226
-
2227
- .status-text {
2228
- font-weight: 500;
2229
- color: #4caf50;
2230
- }
2231
-
2232
- .current-epoch {
2233
- font-weight: 500;
2234
- }
2235
-
2236
- .explanation-block {
2237
- margin-bottom: var(--spacing-md);
2238
- }
2239
-
2240
- .explanation-block h3 {
2241
- font-size: var(--font-size-medium);
2242
- margin-bottom: var(--spacing-xs);
2243
- display: flex;
2244
- align-items: center;
2245
- }
2246
-
2247
- .explanation-block p {
2248
- color: var(--text-secondary);
2249
- font-size: var(--font-size-small);
2250
- display: flex;
2251
- align-items: center;
2252
- }
2253
-
2254
- .gradient-canvas-container {
2255
- background-color: var(--background-off);
2256
- border-radius: var(--border-radius-sm);
2257
- padding: var(--spacing-md);
2258
- display: flex;
2259
- justify-content: center;
2260
- }
2261
-
2262
- .gradient-canvas {
2263
- max-width: 100%;
2264
- }
2265
-
2266
- /* Results Panel */
2267
- .results-panel {
2268
- padding: var(--spacing-md);
2269
- }
2270
-
2271
- .results-header {
2272
- display: flex;
2273
- justify-content: space-between;
2274
- align-items: center;
2275
- margin-bottom: var(--spacing-lg);
2276
- }
2277
-
2278
- .view-toggle {
2279
- display: flex;
2280
- gap: var(--spacing-xs);
2281
- }
2282
-
2283
- .toggle-button {
2284
- padding: var(--spacing-sm) var(--spacing-md);
2285
- border-radius: var(--border-radius-sm);
2286
- font-size: var(--font-size-small);
2287
- transition: all var(--transition-fast);
2288
- }
2289
-
2290
- .toggle-button:hover {
2291
- background-color: var(--background-off);
2292
- }
2293
-
2294
- .toggle-button.active {
2295
- background-color: var(--primary-color);
2296
- color: var(--text-light);
2297
- }
2298
-
2299
- .no-results {
2300
- display: flex;
2301
- flex-direction: column;
2302
- align-items: center;
2303
- justify-content: center;
2304
- min-height: 300px;
2305
- gap: var(--spacing-md);
2306
- color: var(--text-secondary);
2307
- }
2308
-
2309
- .placeholder-icon {
2310
- font-size: 48px;
2311
- opacity: 0.5;
2312
- }
2313
-
2314
- .metrics-grid {
2315
- display: grid;
2316
- grid-template-columns: repeat(2, 1fr);
2317
- gap: var(--spacing-md);
2318
- margin-bottom: var(--spacing-lg);
2319
- }
2320
-
2321
- .metric-card {
2322
- background-color: var(--background-off);
2323
- border-radius: var(--border-radius-sm);
2324
- padding: var(--spacing-md);
2325
- text-align: center;
2326
- }
2327
-
2328
- .metric-value {
2329
- font-size: var(--font-size-xlarge);
2330
- font-weight: 700;
2331
- margin-bottom: var(--spacing-sm);
2332
- }
2333
-
2334
- .metric-value.primary {
2335
- color: var(--primary-color);
2336
- }
2337
-
2338
- .metric-value.privacy-budget {
2339
- font-size: var(--font-size-large);
2340
- }
2341
-
2342
- .metric-value.privacy-budget.excellent {
2343
- color: var(--secondary-color);
2344
- }
2345
-
2346
- .metric-value.privacy-budget.good {
2347
- color: var(--accent-color);
2348
- }
2349
-
2350
- .metric-value.privacy-budget.moderate {
2351
- color: #ff9800;
2352
- }
2353
-
2354
- .metric-value.privacy-budget.weak {
2355
- color: var(--error-color);
2356
- }
2357
-
2358
- .metric-label {
2359
- display: flex;
2360
- justify-content: center;
2361
- align-items: center;
2362
- font-weight: 500;
2363
- color: var(--text-secondary);
2364
- }
2365
-
2366
- .privacy-utility-summary {
2367
- background-color: var(--background-off);
2368
- border-radius: var(--border-radius-sm);
2369
- padding: var(--spacing-md);
2370
- margin-bottom: var(--spacing-lg);
2371
- }
2372
-
2373
- .tradeoff-meter {
2374
- margin-top: var(--spacing-sm);
2375
- }
2376
-
2377
- .meter-bar {
2378
- height: 8px;
2379
- background-color: var(--background-dark);
2380
- border-radius: 4px;
2381
- position: relative;
2382
- margin: var(--spacing-md) 0;
2383
- }
2384
-
2385
- .utility-indicator,
2386
- .privacy-indicator {
2387
- position: absolute;
2388
- top: -20px;
2389
- transform: translateX(-50%);
2390
- }
2391
-
2392
- .utility-indicator {
2393
- color: var(--secondary-color);
2394
- }
2395
-
2396
- .privacy-indicator {
2397
- color: var(--primary-color);
2398
- }
2399
-
2400
- .indicator-label {
2401
- font-weight: 500;
2402
- font-size: var(--font-size-small);
2403
- }
2404
-
2405
- .utility-indicator::after,
2406
- .privacy-indicator::after {
2407
- content: '';
2408
- position: absolute;
2409
- left: 50%;
2410
- top: 100%;
2411
- transform: translateX(-50%);
2412
- width: 2px;
2413
- height: 25px;
2414
- }
2415
-
2416
- .utility-indicator::after {
2417
- background-color: var(--secondary-color);
2418
- }
2419
-
2420
- .privacy-indicator::after {
2421
- background-color: var(--primary-color);
2422
- }
2423
-
2424
- .meter-explanation {
2425
- font-size: var(--font-size-small);
2426
- color: var(--text-secondary);
2427
- }
2428
-
2429
- .recommendation-section {
2430
- background-color: var(--background-off);
2431
- border-radius: var(--border-radius-sm);
2432
- padding: var(--spacing-md);
2433
- }
2434
-
2435
- .recommendations-list {
2436
- list-style: none;
2437
- margin-top: var(--spacing-sm);
2438
- }
2439
-
2440
- .recommendation-item {
2441
- display: flex;
2442
- align-items: flex-start;
2443
- gap: var(--spacing-sm);
2444
- padding: var(--spacing-sm) 0;
2445
- border-bottom: 1px solid var(--border-color);
2446
- }
2447
-
2448
- .recommendation-item:last-child {
2449
- border-bottom: none;
2450
- }
2451
-
2452
- .recommendation-icon {
2453
- font-size: var(--font-size-large);
2454
- }
2455
-
2456
- /* Learning Hub */
2457
- .learning-hub {
2458
- margin-top: var(--spacing-md);
2459
- }
2460
-
2461
- .learning-container {
2462
- display: grid;
2463
- grid-template-columns: 1fr 1.5fr;
2464
- gap: var(--spacing-lg);
2465
- }
2466
-
2467
- .learning-sidebar {
2468
- background-color: var(--background-light);
2469
- border-radius: var(--border-radius-md);
2470
- padding: var(--spacing-lg);
2471
- box-shadow: var(--shadow-sm);
2472
- }
2473
-
2474
- .learning-content {
2475
- background-color: var(--background-light);
2476
- border-radius: var(--border-radius-md);
2477
- padding: var(--spacing-lg);
2478
- box-shadow: var(--shadow-sm);
2479
- }
2480
-
2481
- .learning-steps {
2482
- list-style: none;
2483
- }
2484
-
2485
- .learning-step {
2486
- display: flex;
2487
- align-items: center;
2488
- padding: var(--spacing-sm) 0;
2489
- margin-bottom: var(--spacing-sm);
2490
- cursor: pointer;
2491
- position: relative;
2492
- padding-left: 36px;
2493
- }
2494
-
2495
- .learning-step::before {
2496
- content: '';
2497
- position: absolute;
2498
- left: 14px;
2499
- top: 50%;
2500
- width: 2px;
2501
- height: calc(100% + var(--spacing-sm));
2502
- background-color: var(--primary-light);
2503
- transform: translateY(-50%);
2504
- }
2505
-
2506
- .learning-step:last-child::before {
2507
- height: 50%;
2508
- }
2509
-
2510
- .learning-step:first-child::before {
2511
- top: 75%;
2512
- height: calc(50% + var(--spacing-sm));
2513
- }
2514
-
2515
- .step-indicator {
2516
- position: absolute;
2517
- left: 10px;
2518
- height: 10px;
2519
- width: 10px;
2520
- background-color: var(--primary-light);
2521
- border-radius: 50%;
2522
- z-index: 1;
2523
- }
2524
-
2525
- .step-indicator.completed {
2526
- background-color: var(--secondary-color);
2527
- }
2528
-
2529
- .step-indicator.active {
2530
- background-color: var(--primary-color);
2531
- height: 16px;
2532
- width: 16px;
2533
- left: 7px;
2534
- }
2535
-
2536
- .step-title {
2537
- font-weight: 500;
2538
- color: var(--text-secondary);
2539
- }
2540
-
2541
- .step-title.active {
2542
- color: var(--primary-color);
2543
- }
2544
-
2545
- .step-title.completed {
2546
- color: var(--text-primary);