ShuyaFeng commited on
Commit
5310b3f
·
unverified ·
1 Parent(s): 5a40d07

Add files via upload

Browse files
complete-dpsgd-explorer.html ADDED
@@ -0,0 +1,1654 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,2546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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);