ShuyaFeng commited on
Commit
e788430
·
unverified ·
2 Parent(s): 5310b3f 8ad5d56

Merge pull request #1 from ShuyaFeng/shuya

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