FlameF0X commited on
Commit
19bf1d0
·
verified ·
1 Parent(s): d04be7a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -79
app.py CHANGED
@@ -19,32 +19,32 @@ class DynamicMLPAutoencoder(nn.Module):
19
  """
20
  def __init__(self, input_dim, hidden_dim, latent_dim, num_hidden_layers=1):
21
  super().__init__()
22
-
23
  # Build Encoder
24
  encoder_layers = []
25
  current_dim = input_dim
26
-
27
  # Add progressive downscaling hidden layers
28
  for i in range(num_hidden_layers):
29
  next_dim = max(hidden_dim // (2 ** i), latent_dim * 2)
30
  encoder_layers.append(nn.Linear(current_dim, next_dim))
31
  encoder_layers.append(nn.ReLU())
32
  current_dim = next_dim
33
-
34
  encoder_layers.append(nn.Linear(current_dim, latent_dim))
35
  self.encoder = nn.Sequential(*encoder_layers)
36
-
37
  # Build Decoder
38
  decoder_layers = []
39
  current_dim = latent_dim
40
-
41
  # Add progressive upscaling hidden layers matching the encoder's reverse path
42
  for i in reversed(range(num_hidden_layers)):
43
  next_dim = max(hidden_dim // (2 ** i), latent_dim * 2)
44
  decoder_layers.append(nn.Linear(current_dim, next_dim))
45
  decoder_layers.append(nn.ReLU())
46
  current_dim = next_dim
47
-
48
  decoder_layers.append(nn.Linear(current_dim, input_dim))
49
  decoder_layers.append(nn.Sigmoid()) # Clamp output pixels between [0, 1]
50
  self.decoder = nn.Sequential(*decoder_layers)
@@ -58,21 +58,26 @@ class DynamicMLPAutoencoder(nn.Module):
58
  # 2. HELPER UTILITIES
59
  # =====================================================================
60
 
61
- def preprocess_image(pil_img, resolution):
62
- """Resizes, converts to tensor, and flattens an image."""
63
- img_resized = pil_img.resize((resolution, resolution), Image.Resampling.LANCZOS)
 
 
 
 
 
64
  img_np = np.array(img_resized).astype(np.float32) / 255.0
65
-
66
  # Handle Grayscale / RGBA conversions
67
  if len(img_np.shape) == 2: # Grayscale to RGB
68
  img_np = np.stack([img_np] * 3, axis=-1)
69
  elif img_np.shape[2] == 4: # RGBA to RGB
70
  img_np = img_np[:, :, :3]
71
-
72
  img_tensor = torch.tensor(img_np).permute(2, 0, 1).unsqueeze(0) # Shape: [1, 3, H, W]
73
  return img_tensor, img_resized
74
 
75
- def postprocess_tensor(tensor, resolution):
76
  """Converts flattened/raw image tensors back to PIL Images."""
77
  img_np = tensor.squeeze(0).permute(1, 2, 0).detach().cpu().numpy()
78
  img_np = np.clip(img_np * 255.0, 0, 255).astype(np.uint8)
@@ -98,13 +103,38 @@ def create_loss_plot(losses):
98
  fig.tight_layout()
99
  return fig
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  # =====================================================================
102
  # 3. INTERACTIVE TRAINING FUNCTION (GRADIO GENERATOR)
103
  # =====================================================================
104
 
105
  def run_autoencoder_sandbox(
106
  input_image,
107
- resolution,
 
 
108
  latent_dim,
109
  hidden_dim,
110
  num_hidden_layers,
@@ -117,20 +147,26 @@ def run_autoencoder_sandbox(
117
  if input_image is None:
118
  raise gr.Error("Please upload or select an image first!")
119
 
 
 
 
 
 
 
120
  # 1. Preprocessing
121
- progress(0, desc="Preprocessing image...")
122
- img_tensor, original_resized = preprocess_image(input_image, resolution)
123
-
124
  # Flatten dimensions using .reshape() instead of .view() to avoid layout conflicts
125
- input_dim = 3 * resolution * resolution
126
  flat_clean = img_tensor.reshape(1, -1)
127
-
128
  # 2. Setup Noise (for Denoising mode)
129
  noisy_img_tensor = add_gaussian_noise(img_tensor, noise_factor)
130
  flat_input = noisy_img_tensor.reshape(1, -1)
131
-
132
  # Pre-render the noisy input for user preview
133
- noisy_preview = postprocess_tensor(noisy_img_tensor, resolution)
134
 
135
  # 3. Model Initialization
136
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -140,7 +176,7 @@ def run_autoencoder_sandbox(
140
  latent_dim=latent_dim,
141
  num_hidden_layers=num_hidden_layers
142
  ).to(device)
143
-
144
  flat_input = flat_input.to(device)
145
  flat_clean = flat_clean.to(device)
146
 
@@ -149,48 +185,49 @@ def run_autoencoder_sandbox(
149
  optimizer = optim.Adam(model.parameters(), lr=learning_rate)
150
  else:
151
  optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)
152
-
153
  criterion = nn.MSELoss()
154
  losses = []
155
-
156
  # Determine UI update frequency to preserve execution speed
157
- update_every = max(1, epochs // 40)
158
 
159
  # 5. Training Loop
160
  for epoch in range(epochs):
161
  model.train()
162
  optimizer.zero_grad()
163
-
164
  # Forward Pass
165
  reconstructed, latent = model(flat_input)
166
-
167
  # Calculate loss (against clean image always, to support denoising autoencoder structure)
168
  loss = criterion(reconstructed, flat_clean)
169
-
170
  # Backward Pass & Step
171
  loss.backward()
172
  optimizer.step()
173
-
174
  losses.append(loss.item())
175
-
176
  # Emit live training updates
177
  if epoch % update_every == 0 or epoch == epochs - 1:
178
  progress((epoch + 1) / epochs, desc=f"Epoch {epoch+1}/{epochs} | Loss: {loss.item():.5f}")
179
-
180
  # Format reconstructed image back to normal shape using .reshape()
181
- reconstructed_tensor = reconstructed.reshape(1, 3, resolution, resolution)
182
- reconstructed_pil = postprocess_tensor(reconstructed_tensor, resolution)
183
-
184
  # Generate loss graph
185
  loss_plot = create_loss_plot(losses)
186
-
187
  status_text = (
188
  f"### Training Diagnostics\n"
189
  f"- **Current Epoch:** {epoch + 1} / {epochs}\n"
190
  f"- **Current Loss (MSE):** `{loss.item():.6f}`\n"
 
191
  f"- **Compression Ratio:** `{input_dim} inputs ➜ {latent_dim} bottleneck` (Compacted by **{input_dim / latent_dim:.1f}x**)"
192
  )
193
-
194
  # Yield components iteratively for active visual rendering
195
  yield (
196
  noisy_preview,
@@ -205,6 +242,16 @@ def run_autoencoder_sandbox(
205
  # 4. GRADIO APP INTERFACE LAYOUT
206
  # =====================================================================
207
 
 
 
 
 
 
 
 
 
 
 
208
  with gr.Blocks(theme=gr.themes.Soft(), title="Autoencoder Sandbox") as demo:
209
  gr.Markdown(
210
  """
@@ -212,87 +259,104 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Autoencoder Sandbox") as demo:
212
  Explore how deep neural networks compress, reconstruct, and denoise raw images. By training an autoencoder on *just* this single image, you can observe how limiting the **latent bottleneck** restricts the reconstruction capability—forcing the network to blur details or filter noise!
213
  """
214
  )
215
-
216
  with gr.Row():
217
  # --- LEFT SIDEBAR: CONTROLS & ARCHITECTURE ---
218
  with gr.Column(scale=1):
219
  gr.Markdown("### 🛠️ Step 1: Input & Parameters")
220
  input_img = gr.Image(type="pil", label="Upload Source Image", value=None)
221
-
222
  with gr.Tab("Network Architecture"):
223
- resolution = gr.Dropdown(
224
- choices=[32, 64, 128],
225
- value=64,
226
- label="Image Training Resolution",
227
- info="Higher resolutions take longer to train. 64x64 is recommended."
228
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  latent_dim = gr.Slider(
230
- minimum=1,
231
- maximum=256,
232
- value=16,
233
- step=1,
234
- label="Latent Dimension (Bottleneck)",
235
  info="Lower values yield abstract, blurry representations ('not 1:1')."
236
  )
237
  hidden_dim = gr.Slider(
238
- minimum=16,
239
- maximum=512,
240
- value=128,
241
- step=16,
242
- label="Hidden Dimension",
243
  info="Size of the intermediate neural network layers."
244
  )
245
  num_hidden_layers = gr.Slider(
246
- minimum=1,
247
- maximum=3,
248
- value=1,
249
- step=1,
250
- label="Number of Hidden Layers",
251
  info="Deepens the feature extraction depth."
252
  )
253
-
254
  with gr.Tab("Training Configuration"):
255
  noise_factor = gr.Slider(
256
- minimum=0.0,
257
- maximum=2.0,
258
- value=0.0,
259
- step=0.05,
260
- label="Noise Injection (Denoising Mode)",
261
  info="Introduces random noise to the training inputs. Model learns to clean it!"
262
  )
263
  epochs = gr.Slider(
264
- minimum=20,
265
- maximum=1500,
266
- value=400,
267
- step=20,
268
- label="Training Epochs",
269
  info="Total gradient updates."
270
  )
271
  learning_rate = gr.Slider(
272
- minimum=0.0001,
273
- maximum=0.05,
274
- value=0.005,
275
- step=0.0005,
276
  label="Learning Rate"
277
  )
278
  optimizer_name = gr.Radio(
279
- choices=["Adam", "SGD"],
280
- value="Adam",
281
  label="Optimizer Mode"
282
  )
283
-
284
  train_btn = gr.Button("🚀 Begin Real-Time Training", variant="primary")
285
 
286
  # --- RIGHT SIDEBAR: OUTPUTS & METRICS ---
287
  with gr.Column(scale=2):
288
  gr.Markdown("### 📊 Step 2: Live Training Output")
289
-
290
  with gr.Row():
291
  with gr.Column():
292
  noisy_output_img = gr.Image(label="Model Input (Noisy / Resized)", interactive=False)
293
  with gr.Column():
294
  reconstruction_output_img = gr.Image(label="Live Model Reconstruction", interactive=False)
295
-
296
  with gr.Row():
297
  with gr.Column(scale=1):
298
  diagnostics = gr.Markdown(
@@ -300,14 +364,16 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Autoencoder Sandbox") as demo:
300
  )
301
  with gr.Column(scale=1.2):
302
  loss_curve_plot = gr.Plot(label="Live Loss Curve")
303
-
304
  # Link action to the training function
305
  # Added show_progress="hidden" to suppress output-level flickering/loading animations
306
  train_btn.click(
307
  fn=run_autoencoder_sandbox,
308
  inputs=[
309
  input_img,
310
- resolution,
 
 
311
  latent_dim,
312
  hidden_dim,
313
  num_hidden_layers,
 
19
  """
20
  def __init__(self, input_dim, hidden_dim, latent_dim, num_hidden_layers=1):
21
  super().__init__()
22
+
23
  # Build Encoder
24
  encoder_layers = []
25
  current_dim = input_dim
26
+
27
  # Add progressive downscaling hidden layers
28
  for i in range(num_hidden_layers):
29
  next_dim = max(hidden_dim // (2 ** i), latent_dim * 2)
30
  encoder_layers.append(nn.Linear(current_dim, next_dim))
31
  encoder_layers.append(nn.ReLU())
32
  current_dim = next_dim
33
+
34
  encoder_layers.append(nn.Linear(current_dim, latent_dim))
35
  self.encoder = nn.Sequential(*encoder_layers)
36
+
37
  # Build Decoder
38
  decoder_layers = []
39
  current_dim = latent_dim
40
+
41
  # Add progressive upscaling hidden layers matching the encoder's reverse path
42
  for i in reversed(range(num_hidden_layers)):
43
  next_dim = max(hidden_dim // (2 ** i), latent_dim * 2)
44
  decoder_layers.append(nn.Linear(current_dim, next_dim))
45
  decoder_layers.append(nn.ReLU())
46
  current_dim = next_dim
47
+
48
  decoder_layers.append(nn.Linear(current_dim, input_dim))
49
  decoder_layers.append(nn.Sigmoid()) # Clamp output pixels between [0, 1]
50
  self.decoder = nn.Sequential(*decoder_layers)
 
58
  # 2. HELPER UTILITIES
59
  # =====================================================================
60
 
61
+ def preprocess_image(pil_img, width, height):
62
+ """Resizes (to width x height), converts to tensor, and flattens an image.
63
+
64
+ Note: PIL's `.resize()` takes a (width, height) tuple, which is exactly
65
+ the ordering we want to preserve here so non-square (n x m) resolutions
66
+ work correctly.
67
+ """
68
+ img_resized = pil_img.resize((width, height), Image.Resampling.LANCZOS)
69
  img_np = np.array(img_resized).astype(np.float32) / 255.0
70
+
71
  # Handle Grayscale / RGBA conversions
72
  if len(img_np.shape) == 2: # Grayscale to RGB
73
  img_np = np.stack([img_np] * 3, axis=-1)
74
  elif img_np.shape[2] == 4: # RGBA to RGB
75
  img_np = img_np[:, :, :3]
76
+
77
  img_tensor = torch.tensor(img_np).permute(2, 0, 1).unsqueeze(0) # Shape: [1, 3, H, W]
78
  return img_tensor, img_resized
79
 
80
+ def postprocess_tensor(tensor, width, height):
81
  """Converts flattened/raw image tensors back to PIL Images."""
82
  img_np = tensor.squeeze(0).permute(1, 2, 0).detach().cpu().numpy()
83
  img_np = np.clip(img_np * 255.0, 0, 255).astype(np.uint8)
 
103
  fig.tight_layout()
104
  return fig
105
 
106
+ def resolve_resolution(res_mode, custom_width, custom_height, input_image):
107
+ """
108
+ Determines the final (width, height) to train on based on the selected mode:
109
+ - "Use image's native resolution": pulls straight from the uploaded image
110
+ - Preset choices like "64x64", "128x128", "64x256": parsed directly
111
+ - "Custom": uses the custom_width / custom_height slider values
112
+ """
113
+ if res_mode == "Use image's native resolution":
114
+ if input_image is None:
115
+ raise gr.Error("Please upload an image first so its native resolution can be used!")
116
+ width, height = input_image.size # PIL gives (width, height)
117
+ return width, height
118
+
119
+ if res_mode == "Custom (set width/height below)":
120
+ return int(custom_width), int(custom_height)
121
+
122
+ # Preset like "64x64" or "64x256"
123
+ try:
124
+ w_str, h_str = res_mode.lower().split("x")
125
+ return int(w_str), int(h_str)
126
+ except Exception:
127
+ raise gr.Error(f"Could not parse resolution preset: {res_mode}")
128
+
129
  # =====================================================================
130
  # 3. INTERACTIVE TRAINING FUNCTION (GRADIO GENERATOR)
131
  # =====================================================================
132
 
133
  def run_autoencoder_sandbox(
134
  input_image,
135
+ res_mode,
136
+ custom_width,
137
+ custom_height,
138
  latent_dim,
139
  hidden_dim,
140
  num_hidden_layers,
 
147
  if input_image is None:
148
  raise gr.Error("Please upload or select an image first!")
149
 
150
+ # 0. Resolve target width/height (supports n x n and n x m, or native size)
151
+ width, height = resolve_resolution(res_mode, custom_width, custom_height, input_image)
152
+
153
+ if width < 4 or height < 4:
154
+ raise gr.Error("Width and height must each be at least 4 pixels.")
155
+
156
  # 1. Preprocessing
157
+ progress(0, desc=f"Preprocessing image to {width}x{height}...")
158
+ img_tensor, original_resized = preprocess_image(input_image, width, height)
159
+
160
  # Flatten dimensions using .reshape() instead of .view() to avoid layout conflicts
161
+ input_dim = 3 * width * height
162
  flat_clean = img_tensor.reshape(1, -1)
163
+
164
  # 2. Setup Noise (for Denoising mode)
165
  noisy_img_tensor = add_gaussian_noise(img_tensor, noise_factor)
166
  flat_input = noisy_img_tensor.reshape(1, -1)
167
+
168
  # Pre-render the noisy input for user preview
169
+ noisy_preview = postprocess_tensor(noisy_img_tensor, width, height)
170
 
171
  # 3. Model Initialization
172
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
176
  latent_dim=latent_dim,
177
  num_hidden_layers=num_hidden_layers
178
  ).to(device)
179
+
180
  flat_input = flat_input.to(device)
181
  flat_clean = flat_clean.to(device)
182
 
 
185
  optimizer = optim.Adam(model.parameters(), lr=learning_rate)
186
  else:
187
  optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)
188
+
189
  criterion = nn.MSELoss()
190
  losses = []
191
+
192
  # Determine UI update frequency to preserve execution speed
193
+ update_every = max(1, epochs // 40)
194
 
195
  # 5. Training Loop
196
  for epoch in range(epochs):
197
  model.train()
198
  optimizer.zero_grad()
199
+
200
  # Forward Pass
201
  reconstructed, latent = model(flat_input)
202
+
203
  # Calculate loss (against clean image always, to support denoising autoencoder structure)
204
  loss = criterion(reconstructed, flat_clean)
205
+
206
  # Backward Pass & Step
207
  loss.backward()
208
  optimizer.step()
209
+
210
  losses.append(loss.item())
211
+
212
  # Emit live training updates
213
  if epoch % update_every == 0 or epoch == epochs - 1:
214
  progress((epoch + 1) / epochs, desc=f"Epoch {epoch+1}/{epochs} | Loss: {loss.item():.5f}")
215
+
216
  # Format reconstructed image back to normal shape using .reshape()
217
+ reconstructed_tensor = reconstructed.reshape(1, 3, height, width)
218
+ reconstructed_pil = postprocess_tensor(reconstructed_tensor, width, height)
219
+
220
  # Generate loss graph
221
  loss_plot = create_loss_plot(losses)
222
+
223
  status_text = (
224
  f"### Training Diagnostics\n"
225
  f"- **Current Epoch:** {epoch + 1} / {epochs}\n"
226
  f"- **Current Loss (MSE):** `{loss.item():.6f}`\n"
227
+ f"- **Resolution:** `{width} x {height}` ({input_dim} input values)\n"
228
  f"- **Compression Ratio:** `{input_dim} inputs ➜ {latent_dim} bottleneck` (Compacted by **{input_dim / latent_dim:.1f}x**)"
229
  )
230
+
231
  # Yield components iteratively for active visual rendering
232
  yield (
233
  noisy_preview,
 
242
  # 4. GRADIO APP INTERFACE LAYOUT
243
  # =====================================================================
244
 
245
+ RESOLUTION_PRESETS = [
246
+ "Use image's native resolution",
247
+ "32x32",
248
+ "64x64",
249
+ "128x128",
250
+ "64x256",
251
+ "256x64",
252
+ "Custom (set width/height below)",
253
+ ]
254
+
255
  with gr.Blocks(theme=gr.themes.Soft(), title="Autoencoder Sandbox") as demo:
256
  gr.Markdown(
257
  """
 
259
  Explore how deep neural networks compress, reconstruct, and denoise raw images. By training an autoencoder on *just* this single image, you can observe how limiting the **latent bottleneck** restricts the reconstruction capability—forcing the network to blur details or filter noise!
260
  """
261
  )
262
+
263
  with gr.Row():
264
  # --- LEFT SIDEBAR: CONTROLS & ARCHITECTURE ---
265
  with gr.Column(scale=1):
266
  gr.Markdown("### 🛠️ Step 1: Input & Parameters")
267
  input_img = gr.Image(type="pil", label="Upload Source Image", value=None)
268
+
269
  with gr.Tab("Network Architecture"):
270
+ res_mode = gr.Dropdown(
271
+ choices=RESOLUTION_PRESETS,
272
+ value="64x64",
273
+ label="Training Resolution",
274
+ info="Pick a preset (n x n or n x m), use the image's native size, or set a custom width/height below."
275
  )
276
+ with gr.Row():
277
+ custom_width = gr.Slider(
278
+ minimum=8,
279
+ maximum=512,
280
+ value=64,
281
+ step=8,
282
+ label="Custom Width",
283
+ info="Only used when 'Custom' is selected above."
284
+ )
285
+ custom_height = gr.Slider(
286
+ minimum=8,
287
+ maximum=512,
288
+ value=64,
289
+ step=8,
290
+ label="Custom Height",
291
+ info="Only used when 'Custom' is selected above."
292
+ )
293
  latent_dim = gr.Slider(
294
+ minimum=1,
295
+ maximum=256,
296
+ value=16,
297
+ step=1,
298
+ label="Latent Dimension (Bottleneck)",
299
  info="Lower values yield abstract, blurry representations ('not 1:1')."
300
  )
301
  hidden_dim = gr.Slider(
302
+ minimum=16,
303
+ maximum=512,
304
+ value=128,
305
+ step=16,
306
+ label="Hidden Dimension",
307
  info="Size of the intermediate neural network layers."
308
  )
309
  num_hidden_layers = gr.Slider(
310
+ minimum=1,
311
+ maximum=3,
312
+ value=1,
313
+ step=1,
314
+ label="Number of Hidden Layers",
315
  info="Deepens the feature extraction depth."
316
  )
317
+
318
  with gr.Tab("Training Configuration"):
319
  noise_factor = gr.Slider(
320
+ minimum=0.0,
321
+ maximum=2.0,
322
+ value=0.0,
323
+ step=0.05,
324
+ label="Noise Injection (Denoising Mode)",
325
  info="Introduces random noise to the training inputs. Model learns to clean it!"
326
  )
327
  epochs = gr.Slider(
328
+ minimum=20,
329
+ maximum=1500,
330
+ value=400,
331
+ step=20,
332
+ label="Training Epochs",
333
  info="Total gradient updates."
334
  )
335
  learning_rate = gr.Slider(
336
+ minimum=0.0001,
337
+ maximum=0.05,
338
+ value=0.005,
339
+ step=0.0005,
340
  label="Learning Rate"
341
  )
342
  optimizer_name = gr.Radio(
343
+ choices=["Adam", "SGD"],
344
+ value="Adam",
345
  label="Optimizer Mode"
346
  )
347
+
348
  train_btn = gr.Button("🚀 Begin Real-Time Training", variant="primary")
349
 
350
  # --- RIGHT SIDEBAR: OUTPUTS & METRICS ---
351
  with gr.Column(scale=2):
352
  gr.Markdown("### 📊 Step 2: Live Training Output")
353
+
354
  with gr.Row():
355
  with gr.Column():
356
  noisy_output_img = gr.Image(label="Model Input (Noisy / Resized)", interactive=False)
357
  with gr.Column():
358
  reconstruction_output_img = gr.Image(label="Live Model Reconstruction", interactive=False)
359
+
360
  with gr.Row():
361
  with gr.Column(scale=1):
362
  diagnostics = gr.Markdown(
 
364
  )
365
  with gr.Column(scale=1.2):
366
  loss_curve_plot = gr.Plot(label="Live Loss Curve")
367
+
368
  # Link action to the training function
369
  # Added show_progress="hidden" to suppress output-level flickering/loading animations
370
  train_btn.click(
371
  fn=run_autoencoder_sandbox,
372
  inputs=[
373
  input_img,
374
+ res_mode,
375
+ custom_width,
376
+ custom_height,
377
  latent_dim,
378
  hidden_dim,
379
  num_hidden_layers,