PowerMachine commited on
Commit
2321f41
·
verified ·
1 Parent(s): f05693f

Delete utils/vqvae2.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. utils/vqvae2.py +0 -570
utils/vqvae2.py DELETED
@@ -1,570 +0,0 @@
1
- """vqvae2.py — VQ-VAE-2 Hierárquico para compressão multimodal.
2
-
3
- Implementa Vector Quantized Variational Autoencoder 2 (Roy et al., 2019),
4
- uma versão hierárquica do VQ-VAE que usa dois níveis de quantização:
5
- - Top level: captura padrões semânticos globais (low-resolution)
6
- - Bottom level: captura detalhes locais (high-resolution)
7
-
8
- ==============================================================================
9
- ANÁLISE MATEMÁTICA E LÓGICA
10
- ==============================================================================
11
-
12
- ARQUITETURA:
13
- Input x ∈ R^{B×C×H×W}
14
- |
15
- v
16
- [Bottom Encoder] -> z_b ∈ R^{B×d_b×H×W}
17
- |
18
- v
19
- [Top Encoder] -> z_t ∈ R^{B×d_t×H/2×W/2}
20
- |
21
- v
22
- [Top Quantizer] -> z_t_q (codebook lookup)
23
- |
24
- v
25
- [Top Decoder] -> z_t_dec ∈ R^{B×d_b×H×W}
26
- |
27
- v
28
- [Bottom Quantizer with z_t_dec] -> z_b_q
29
- |
30
- v
31
- [Bottom Decoder] -> x_recon
32
-
33
- LOSS:
34
- L = L_recon + beta * L_commit_top + beta * L_commit_bottom
35
- + gamma * L_codebook_top + gamma * L_codebook_bottom
36
-
37
- onde:
38
- L_recon = MSE(x, x_recon) ou L1
39
- L_commit_k = ||sg(z_k) - e_k||^2 (commit loss)
40
- L_codebook_k = ||z_k - sg(e_k)||^2 (codebook loss)
41
- sg = stop-gradient
42
-
43
- Update via EMA (Exponential Moving Average) — alternativo ao gradiente:
44
- n_k^{t+1} = gamma_ema * n_k^t + (1 - gamma_ema) * sum_k 1[assigned]
45
- m_k^{t+1} = gamma_ema * m_k^t + (1 - gamma_ema) * sum_k z_k
46
- e_k = m_k / n_k
47
-
48
- CODEBOOK:
49
- Top codebook: K_t vetores de dimensão d_t (tipicamente 512-1024 vetores)
50
- Bottom codebook: K_b vetores de dimensão d_b (tipicamente 512-1024 vetores)
51
-
52
- ==============================================================================
53
- INTEGRAÇÃO COM CNN-BiGRU
54
- ==============================================================================
55
-
56
- O VQ-VAE-2 é usado para comprimir representações multimodais (imagem, áudio)
57
- em códigos discretos que podem ser processados pelo backbone CNN-BiGRU:
58
-
59
- 1. Imagem -> VQ-VAE-2 -> códigos top + bottom (tokens discretos)
60
- 2. Códigos -> embedding lookup -> [B, T, D]
61
- 3. [B, T, D] -> CooperativeCNNBiGRU (como se fosse texto tokenizado)
62
-
63
- Isto permite que o modelo trate imagem/áudio da mesma forma que texto,
64
- unificando o pipeline multimodal.
65
-
66
- Autor: CNN-BiGRU Project
67
- """
68
- from __future__ import annotations
69
-
70
- import logging
71
- import math
72
- from dataclasses import dataclass, field
73
- from typing import Dict, List, Optional, Tuple
74
-
75
- import torch
76
- import torch.nn as nn
77
- import torch.nn.functional as F
78
-
79
- logger = logging.getLogger(__name__)
80
-
81
-
82
- # ============================================================================
83
- # Configuração
84
- # ============================================================================
85
-
86
- @dataclass
87
- class VQVAE2Config:
88
- """Configuração do VQ-VAE-2 hierárquico."""
89
- # Dimensões de entrada (assumindo features [B, C, H, W])
90
- in_channels: int = 1
91
- # Dimensões latentes
92
- bottom_channels: int = 64 # d_b
93
- top_channels: int = 32 # d_t
94
- # Tamanhos dos codebooks
95
- n_bottom_codes: int = 512 # K_b
96
- n_top_codes: int = 512 # K_t
97
- # Beta (commit loss weight)
98
- commitment_cost: float = 0.25
99
- # Gamma (codebook loss weight — apenas para grad update, não EMA)
100
- codebook_cost: float = 0.0
101
- # Usar EMA para atualizar codebook (default: True)
102
- use_ema: bool = True
103
- ema_decay: float = 0.99
104
- # Usar residual connection (z + delta)
105
- use_residual: bool = False
106
- # Número de camadas de down/upsampling
107
- n_downsample: int = 2
108
- # Base channels do encoder/decoder
109
- hidden_channels: int = 64
110
- # Device
111
- device: str = "cpu"
112
-
113
-
114
- # ============================================================================
115
- # Residual Blocks (para encoder/decoder)
116
- # ============================================================================
117
-
118
- def _adaptive_groups(channels: int, max_groups: int = 8) -> int:
119
- """Computa número de grupos para GroupNorm que divide channels."""
120
- if channels <= 0:
121
- return 1
122
- g = min(max_groups, channels)
123
- while channels % g != 0 and g > 1:
124
- g -= 1
125
- return max(1, g)
126
-
127
-
128
- class ResidualBlock(nn.Module):
129
- """Bloco residual simples com Conv2d + GroupNorm + SiLU."""
130
-
131
- def __init__(self, channels: int, hidden_channels: Optional[int] = None):
132
- super().__init__()
133
- h = hidden_channels or channels
134
- self.conv1 = nn.Conv2d(channels, h, kernel_size=3, padding=1)
135
- self.conv2 = nn.Conv2d(h, channels, kernel_size=3, padding=1)
136
- # GroupNorm adaptativo por número de canais
137
- self.norm1 = nn.GroupNorm(_adaptive_groups(channels), channels)
138
- # norm2 aplica-se à saída de conv1 (h canais)
139
- self.norm2 = nn.GroupNorm(_adaptive_groups(h), h)
140
-
141
- def forward(self, x: torch.Tensor) -> torch.Tensor:
142
- h = F.silu(self.norm1(x))
143
- h = self.conv1(h)
144
- h = F.silu(self.norm2(h))
145
- h = self.conv2(h)
146
- return x + h
147
-
148
-
149
- # ============================================================================
150
- # Encoder / Decoder (Conv2d-based)
151
- # ============================================================================
152
-
153
- class ConvEncoder(nn.Module):
154
- """Encoder convolucional: reduz resolução espacial por fator 2^n_downsample."""
155
-
156
- def __init__(
157
- self,
158
- in_channels: int,
159
- out_channels: int,
160
- hidden_channels: int = 64,
161
- n_downsample: int = 2,
162
- ):
163
- super().__init__()
164
- layers = []
165
- c_in = in_channels
166
- for i in range(n_downsample):
167
- c_out = hidden_channels * (2 ** i)
168
- layers.extend([
169
- nn.Conv2d(c_in, c_out, kernel_size=4, stride=2, padding=1),
170
- nn.GroupNorm(_adaptive_groups(c_out), c_out),
171
- nn.SiLU(),
172
- ])
173
- c_in = c_out
174
- # Residual blocks para refinar
175
- layers.append(ResidualBlock(c_in, out_channels))
176
- # Projeta para out_channels
177
- layers.append(nn.Conv2d(c_in, out_channels, kernel_size=1))
178
- self.net = nn.Sequential(*layers)
179
-
180
- def forward(self, x: torch.Tensor) -> torch.Tensor:
181
- return self.net(x)
182
-
183
-
184
- class ConvDecoder(nn.Module):
185
- """Decoder convolucional: aumenta resolução espacial por fator 2^n_upsample."""
186
-
187
- def __init__(
188
- self,
189
- in_channels: int,
190
- out_channels: int,
191
- hidden_channels: int = 64,
192
- n_upsample: int = 2,
193
- ):
194
- super().__init__()
195
- layers = []
196
- # Primeiro projeta de in_channels para hidden_channels * 2^(n-1)
197
- c_start = hidden_channels * (2 ** (n_upsample - 1))
198
- layers.append(nn.Conv2d(in_channels, c_start, kernel_size=1))
199
- layers.append(ResidualBlock(c_start, c_start))
200
-
201
- c_in = c_start
202
- for i in range(n_upsample - 1, -1, -1):
203
- c_out = hidden_channels * (2 ** max(0, i - 1)) if i > 0 else out_channels
204
- layers.extend([
205
- nn.ConvTranspose2d(c_in, c_out, kernel_size=4, stride=2, padding=1),
206
- nn.GroupNorm(_adaptive_groups(c_out), c_out) if i > 0 else nn.Identity(),
207
- nn.SiLU() if i > 0 else nn.Identity(),
208
- ])
209
- c_in = c_out
210
- # Camada final 1x1 para ajustar canais
211
- layers.append(nn.Conv2d(c_in, out_channels, kernel_size=1))
212
- self.net = nn.Sequential(*layers)
213
-
214
- def forward(self, x: torch.Tensor) -> torch.Tensor:
215
- return self.net(x)
216
-
217
-
218
- # ============================================================================
219
- # Vector Quantizer (com EMA opcional)
220
- # ============================================================================
221
-
222
- class VectorQuantizer(nn.Module):
223
- """Quantizador por codebook com Straight-Through Estimator (STE).
224
-
225
- Args:
226
- n_codes: número de códigos no codebook (K)
227
- dim: dimensão de cada código (d)
228
- commitment_cost: peso da commitment loss
229
- use_ema: se True, atualiza codebook via EMA em vez de gradiente
230
- ema_decay: decay do EMA
231
- """
232
-
233
- def __init__(
234
- self,
235
- n_codes: int,
236
- dim: int,
237
- commitment_cost: float = 0.25,
238
- use_ema: bool = True,
239
- ema_decay: float = 0.99,
240
- ):
241
- super().__init__()
242
- self.n_codes = n_codes
243
- self.dim = dim
244
- self.commitment_cost = commitment_cost
245
- self.use_ema = use_ema
246
- self.ema_decay = ema_decay
247
-
248
- # Codebook: [K, d]
249
- codebook = torch.randn(n_codes, dim) * 0.01
250
- if use_ema:
251
- self.register_buffer("codebook", codebook)
252
- self.register_buffer("ema_count", torch.zeros(n_codes))
253
- self.register_buffer("ema_weight", codebook.clone())
254
- else:
255
- self.codebook = nn.Parameter(codebook)
256
-
257
- def forward(
258
- self,
259
- z: torch.Tensor,
260
- ) -> Tuple[torch.Tensor, torch.Tensor, Dict[str, torch.Tensor]]:
261
- """Quantiza z para o codebook mais próximo.
262
-
263
- Args:
264
- z: [B, d, H, W] ou [B, d, T]
265
-
266
- Returns:
267
- z_q: quantizado [B, d, H, W]
268
- indices: [B, H, W] índices do codebook
269
- loss_dict: dict com commitment loss, codebook loss, etc.
270
- """
271
- # Reshape z para [N, d] onde N = B * H * W
272
- original_shape = z.shape
273
- if z.dim() == 4:
274
- B, d, H, W = z.shape
275
- z_flat = z.permute(0, 2, 3, 1).reshape(-1, d) # [B*H*W, d]
276
- elif z.dim() == 3:
277
- B, d, T = z.shape
278
- z_flat = z.permute(0, 2, 1).reshape(-1, d) # [B*T, d]
279
- else:
280
- z_flat = z
281
-
282
- # Distância euclidiana ao codebook
283
- # ||z - e||^2 = ||z||^2 - 2*z·e + ||e||^2
284
- dist = (
285
- z_flat.pow(2).sum(dim=-1, keepdim=True)
286
- - 2 * z_flat @ self.codebook.t()
287
- + self.codebook.pow(2).sum(dim=-1, keepdim=False).unsqueeze(0)
288
- ) # [N, K]
289
-
290
- # Encontrar código mais próximo
291
- indices = dist.argmin(dim=-1) # [N]
292
- one_hot = F.one_hot(indices, self.n_codes).float() # [N, K]
293
-
294
- # Quantizado
295
- z_q = one_hot @ self.codebook # [N, d]
296
-
297
- # Commitment loss
298
- commit_loss = F.mse_loss(z_flat, z_q.detach())
299
- # Codebook loss (apenas se não usar EMA)
300
- if not self.use_ema:
301
- codebook_loss = F.mse_loss(z_q, z_flat.detach())
302
- else:
303
- codebook_loss = torch.zeros(1, device=z.device)
304
-
305
- # EMA update
306
- if self.use_ema and self.training:
307
- with torch.no_grad():
308
- # Conta quantas vezes cada código foi usado
309
- code_count = one_hot.sum(dim=0) # [K]
310
- # Soma dos z_flat para cada código
311
- code_sum = one_hot.t() @ z_flat # [K, d]
312
-
313
- # EMA
314
- self.ema_count.mul_(self.ema_decay).add_(
315
- code_count, alpha=1 - self.ema_decay
316
- )
317
- self.ema_weight.mul_(self.ema_decay).add_(
318
- code_sum, alpha=1 - self.ema_decay
319
- )
320
- # Atualiza codebook
321
- self.codebook.copy_(self.ema_weight / self.ema_count.unsqueeze(-1).clamp(min=1e-8))
322
-
323
- # Straight-Through Estimator: z_q = z + (z_q - z).detach()
324
- z_q_st = z_flat + (z_q - z_flat).detach()
325
-
326
- # Reshape de volta
327
- if z.dim() == 4:
328
- z_q_st = z_q_st.view(B, H, W, d).permute(0, 3, 1, 2).contiguous()
329
- indices = indices.view(B, H, W)
330
- elif z.dim() == 3:
331
- z_q_st = z_q_st.view(B, T, d).permute(0, 2, 1).contiguous()
332
- indices = indices.view(B, T)
333
- else:
334
- z_q_st = z_q_st.view(original_shape)
335
-
336
- # Taxa de uso do codebook (para diagnóstico)
337
- with torch.no_grad():
338
- used = (one_hot.sum(dim=0) > 0).float().sum()
339
- usage = used / self.n_codes
340
-
341
- loss_dict = {
342
- "commit_loss": commit_loss * self.commitment_cost,
343
- "codebook_loss": codebook_loss,
344
- "usage": usage.detach(),
345
- }
346
-
347
- return z_q_st, indices, loss_dict
348
-
349
-
350
- # ============================================================================
351
- # VQ-VAE-2 Hierárquico
352
- # ============================================================================
353
-
354
- class VQVAE2(nn.Module):
355
- """VQ-VAE-2 Hierárquico.
356
-
357
- Estrutura:
358
- x -> BottomEnc -> z_b -> TopEnc -> z_t -> TopQ -> z_t_q
359
- |
360
- v
361
- TopDec -> z_t_dec
362
- |
363
- v
364
- z_b + z_t_dec -> BottomQ -> z_b_q -> BottomDec -> x_recon
365
- """
366
-
367
- def __init__(self, config: VQVAE2Config):
368
- super().__init__()
369
- self.config = config
370
-
371
- # Bottom encoder/decoder
372
- self.bottom_encoder = ConvEncoder(
373
- in_channels=config.in_channels,
374
- out_channels=config.bottom_channels,
375
- hidden_channels=config.hidden_channels,
376
- n_downsample=config.n_downsample,
377
- )
378
- self.bottom_decoder = ConvDecoder(
379
- in_channels=config.bottom_channels,
380
- out_channels=config.in_channels,
381
- hidden_channels=config.hidden_channels,
382
- n_upsample=config.n_downsample,
383
- )
384
-
385
- # Top encoder/decoder (operam sobre z_b)
386
- self.top_encoder = ConvEncoder(
387
- in_channels=config.bottom_channels,
388
- out_channels=config.top_channels,
389
- hidden_channels=config.hidden_channels,
390
- n_downsample=1, # downsample adicional por 2
391
- )
392
- self.top_decoder = ConvDecoder(
393
- in_channels=config.top_channels,
394
- out_channels=config.bottom_channels,
395
- hidden_channels=config.hidden_channels,
396
- n_upsample=1,
397
- )
398
-
399
- # Quantizadores
400
- self.top_quantizer = VectorQuantizer(
401
- n_codes=config.n_top_codes,
402
- dim=config.top_channels,
403
- commitment_cost=config.commitment_cost,
404
- use_ema=config.use_ema,
405
- ema_decay=config.ema_decay,
406
- )
407
- self.bottom_quantizer = VectorQuantizer(
408
- n_codes=config.n_bottom_codes,
409
- dim=config.bottom_channels,
410
- commitment_cost=config.commitment_cost,
411
- use_ema=config.use_ema,
412
- ema_decay=config.ema_decay,
413
- )
414
-
415
- def forward(
416
- self,
417
- x: torch.Tensor,
418
- ) -> Dict[str, torch.Tensor]:
419
- """Forward pass completo.
420
-
421
- Args:
422
- x: [B, C, H, W] entrada (imagem, espectrograma, etc.)
423
-
424
- Returns:
425
- dict com:
426
- x_recon: [B, C, H, W] reconstrução
427
- top_indices: [B, H_t, W_t] índices top
428
- bottom_indices: [B, H_b, W_b] índices bottom
429
- loss: loss total (recon + commit + codebook)
430
- loss_dict: breakdown
431
- """
432
- # 1. Bottom encoder
433
- z_b = self.bottom_encoder(x) # [B, d_b, H_b, W_b]
434
-
435
- # 2. Top encoder (sobre z_b)
436
- z_t = self.top_encoder(z_b) # [B, d_t, H_t, W_t]
437
-
438
- # 3. Top quantizer
439
- z_t_q, top_indices, top_loss = self.top_quantizer(z_t)
440
-
441
- # 4. Top decoder (reconstroi z_b aproximado do top)
442
- z_t_dec = self.top_decoder(z_t_q) # [B, d_b, H_b, W_b]
443
-
444
- # 5. Bottom quantizer sobre (z_b + z_t_dec) ou (z_b - z_t_dec)
445
- if self.config.use_residual:
446
- z_b_residual = z_b - z_t_dec
447
- z_b_q, bottom_indices, bottom_loss = self.bottom_quantizer(z_b_residual)
448
- z_b_full = z_b_q + z_t_dec # reconstrói
449
- else:
450
- z_b_q, bottom_indices, bottom_loss = self.bottom_quantizer(z_b)
451
- z_b_full = z_b_q + z_t_dec # combina top + bottom
452
-
453
- # 6. Bottom decoder
454
- x_recon = self.bottom_decoder(z_b_full) # [B, C, H, W]
455
-
456
- # 7. Loss
457
- recon_loss = F.mse_loss(x_recon, x)
458
- total_loss = (
459
- recon_loss
460
- + top_loss["commit_loss"]
461
- + bottom_loss["commit_loss"]
462
- + top_loss["codebook_loss"]
463
- + bottom_loss["codebook_loss"]
464
- )
465
-
466
- return {
467
- "x_recon": x_recon,
468
- "top_indices": top_indices,
469
- "bottom_indices": bottom_indices,
470
- "loss": total_loss,
471
- "loss_dict": {
472
- "recon_loss": recon_loss.detach(),
473
- "top_commit": top_loss["commit_loss"].detach(),
474
- "bottom_commit": bottom_loss["commit_loss"].detach(),
475
- "top_usage": top_loss["usage"],
476
- "bottom_usage": bottom_loss["usage"],
477
- },
478
- }
479
-
480
- def encode(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
481
- """Apenas encoder (para uso em pipeline multimodal).
482
-
483
- Returns:
484
- (top_indices, bottom_indices) — códigos discretos
485
- """
486
- z_b = self.bottom_encoder(x)
487
- z_t = self.top_encoder(z_b)
488
- _, top_idx, _ = self.top_quantizer(z_t)
489
- z_t_dec = self.top_decoder(self.top_quantizer.codebook[top_idx].permute(
490
- 0, 3, 1, 2
491
- ) if top_idx.dim() == 3 else self.top_quantizer.codebook[top_idx])
492
- # Simplificado: apenas retorna top_idx e bottom_idx brutos
493
- if self.config.use_residual:
494
- z_b_resid = z_b - z_t_dec
495
- _, bottom_idx, _ = self.bottom_quantizer(z_b_resid)
496
- else:
497
- _, bottom_idx, _ = self.bottom_quantizer(z_b)
498
- return top_idx, bottom_idx
499
-
500
- def decode_codes(
501
- self,
502
- top_indices: torch.Tensor,
503
- bottom_indices: torch.Tensor,
504
- ) -> torch.Tensor:
505
- """Decodifica a partir de códigos discretos."""
506
- # Top
507
- z_t_q = self.top_quantizer.codebook[top_indices] # [B, H_t, W_t, d_t]
508
- # Adicionar dim de canal se necessário
509
- if z_t_q.dim() == 4:
510
- z_t_q = z_t_q.permute(0, 3, 1, 2).contiguous()
511
- z_t_dec = self.top_decoder(z_t_q)
512
-
513
- # Bottom
514
- z_b_q = self.bottom_quantizer.codebook[bottom_indices]
515
- if z_b_q.dim() == 4:
516
- z_b_q = z_b_q.permute(0, 3, 1, 2).contiguous()
517
- z_b_full = z_b_q + z_t_dec
518
-
519
- # Decoder
520
- x_recon = self.bottom_decoder(z_b_full)
521
- return x_recon
522
-
523
-
524
- # ============================================================================
525
- # Self-test
526
- # ============================================================================
527
-
528
- def _self_test():
529
- """Teste rápido do VQ-VAE-2."""
530
- torch.manual_seed(42)
531
- config = VQVAE2Config(
532
- in_channels=1,
533
- bottom_channels=16,
534
- top_channels=8,
535
- n_bottom_codes=64,
536
- n_top_codes=64,
537
- n_downsample=2,
538
- hidden_channels=16,
539
- use_ema=True,
540
- commitment_cost=0.25,
541
- )
542
- vqvae = VQVAE2(config)
543
- print(f"VQ-VAE-2 params: {sum(p.numel() for p in vqvae.parameters())}")
544
-
545
- # Forward
546
- x = torch.randn(2, 1, 16, 16)
547
- out = vqvae(x)
548
- print(f"Recon: {out['x_recon'].shape}")
549
- print(f"Top indices: {out['top_indices'].shape}")
550
- print(f"Bottom indices: {out['bottom_indices'].shape}")
551
- print(f"Loss: {out['loss'].item():.4f}")
552
- print(f"Loss dict: {out['loss_dict']}")
553
-
554
- # Test encode/decode
555
- top_idx, bot_idx = vqvae.encode(x)
556
- print(f"Encode: top {top_idx.shape}, bot {bot_idx.shape}")
557
-
558
-
559
- if __name__ == "__main__":
560
- _self_test()
561
-
562
-
563
- __all__ = [
564
- "VQVAE2Config",
565
- "ResidualBlock",
566
- "ConvEncoder",
567
- "ConvDecoder",
568
- "VectorQuantizer",
569
- "VQVAE2",
570
- ]