PowerMachine commited on
Commit
f19e209
·
verified ·
1 Parent(s): 0f4a735

Delete models/multimodal_model.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. models/multimodal_model.py +0 -214
models/multimodal_model.py DELETED
@@ -1,214 +0,0 @@
1
- """multimodal_model.py — Modelo multimodal CNN-BiGRU completo.
2
-
3
- Combina:
4
- - CooperativeCNNBiGRU para texto (dual-stream A/B)
5
- - ImageEncoder para modalidade visual
6
- - AudioEncoder para modalidade auditiva
7
- - MultimodalFusion com gated fusion
8
- - Cabeçalho de classificação + cabeçalho de geração (LM head)
9
-
10
- Suporta dois modos:
11
- mode="classify": retorna logits de classificação [B, num_classes]
12
- mode="generate": retorna logits de LM [B, T, vocab_size] (autoregressivo)
13
- """
14
- from __future__ import annotations
15
-
16
- import logging
17
- from typing import Optional
18
-
19
- import torch
20
- import torch.nn as nn
21
- import torch.nn.functional as F
22
-
23
- from .cooperative_bigru import CooperativeCNNBiGRU
24
- from .multimodal_encoders import ImageEncoder, AudioEncoder, MultimodalFusion
25
-
26
- logger = logging.getLogger(__name__)
27
-
28
-
29
- class MultimodalCNNBiGRU(nn.Module):
30
- """Modelo multimodal completo: texto (dual A/B) + imagem + áudio.
31
-
32
- Fluxo:
33
- 1. Texto A e Texto B -> CooperativeCNNBiGRU -> fused [B, 256]
34
- 2. Imagem -> ImageEncoder -> mean-pool -> [B, D_img]
35
- 3. Áudio -> AudioEncoder -> mean-pool -> [B, D_aud]
36
- 4. Fusão multimodal -> [B, out_dim]
37
- 5. Classificação: Linear(out_dim, num_classes)
38
- 6. Geração (opcional): Linear(out_dim, vocab_size) autoregressivo
39
- """
40
-
41
- def __init__(
42
- self,
43
- vocab_size: int,
44
- num_classes: int = 3,
45
- embedding_dim: int = 64,
46
- cnn_filters: int = 64,
47
- gru_hidden: int = 64,
48
- n_heads: int = 4,
49
- dropout: float = 0.1,
50
- pad_idx: int = 1,
51
- img_channels: int = 1,
52
- img_hidden: int = 32,
53
- img_out_dim: int = 64,
54
- audio_freq: int = 40,
55
- audio_hidden: int = 32,
56
- audio_out_dim: int = 64,
57
- fusion_dim: int = 128,
58
- use_spectral_norm: bool = False,
59
- weight_tying: bool = False,
60
- ):
61
- super().__init__()
62
- self.pad_idx = pad_idx
63
- self.vocab_size = vocab_size
64
- self.num_classes = num_classes
65
- self.weight_tying = weight_tying
66
-
67
- # Núcleo cooperativo de texto
68
- self.text_core = CooperativeCNNBiGRU(
69
- vocab_size=vocab_size,
70
- embedding_dim=embedding_dim,
71
- cnn_filters=cnn_filters,
72
- gru_hidden=gru_hidden,
73
- n_heads=n_heads,
74
- dropout=dropout,
75
- pad_idx=pad_idx,
76
- use_spectral_norm=use_spectral_norm,
77
- )
78
- d_text = self.text_core.feat_dim * 2 # 256 (fused A+B = 2 * feat_dim_per_stream)
79
-
80
- # Encoders multimodais
81
- self.image_encoder = ImageEncoder(
82
- in_channels=img_channels,
83
- out_dim=img_out_dim,
84
- hidden_dim=img_hidden,
85
- )
86
- self.audio_encoder = AudioEncoder(
87
- n_freq=audio_freq,
88
- out_dim=audio_out_dim,
89
- hidden_dim=audio_hidden,
90
- )
91
-
92
- # Pooling de imagem/áudio para vetor fixo
93
- self.img_proj = nn.Linear(img_out_dim, img_out_dim)
94
- self.aud_proj = nn.Linear(audio_out_dim, audio_out_dim)
95
-
96
- # Fusão multimodal
97
- self.fusion = MultimodalFusion(
98
- d_text=d_text,
99
- d_img=img_out_dim,
100
- d_aud=audio_out_dim,
101
- out_dim=fusion_dim,
102
- dropout=dropout,
103
- )
104
-
105
- # Cabeçalho de classificação
106
- self.classifier = nn.Linear(fusion_dim, num_classes)
107
-
108
- # Cabeçalho de geração (LM head) — para modo autoregressivo
109
- self.lm_head = nn.Linear(fusion_dim, vocab_size, bias=False)
110
-
111
- # Weight tying opcional (dados.txt linha 632):
112
- # compartilha pesos entre token_embedding e lm_head.
113
- # Como lm_head é Linear(fusion_dim, vocab_size) e embedding é
114
- # Linear(vocab_size, embedding_dim), o tying direto só funciona se
115
- # fusion_dim == embedding_dim. Caso contrário, usamos uma matriz
116
- # intermediária de projeção.
117
- if weight_tying:
118
- if fusion_dim == self.text_core.embedding.embedding_dim:
119
- self.lm_head.weight = self.text_core.embedding.weight
120
- else:
121
- # Projeção intermediária: embedding_dim -> fusion_dim
122
- self.tie_proj = nn.Linear(
123
- self.text_core.embedding.embedding_dim, fusion_dim, bias=False
124
- )
125
- # tying via forward (ver mode="generate")
126
- logger.info(
127
- f"Weight tying: fusion_dim ({fusion_dim}) != embedding_dim "
128
- f"({self.text_core.embedding.embedding_dim}); usando proj intermediária."
129
- )
130
-
131
- # Dropout final
132
- self.dropout = nn.Dropout(dropout)
133
-
134
- def forward(
135
- self,
136
- input_ids_a: torch.Tensor,
137
- input_ids_b: torch.Tensor,
138
- images: Optional[torch.Tensor] = None,
139
- audios: Optional[torch.Tensor] = None,
140
- attn_mask_a: Optional[torch.Tensor] = None,
141
- attn_mask_b: Optional[torch.Tensor] = None,
142
- mode: str = "classify",
143
- ) -> dict:
144
- """
145
- Args:
146
- input_ids_a, input_ids_b: [B, T] tokens dos streams A e B
147
- images: [B, C, H, W] ou None
148
- audios: [B, 1, T, F] ou None
149
- mode: "classify" | "generate"
150
-
151
- Returns:
152
- dict com logits (classificação ou geração) + features intermediárias
153
- """
154
- # 1. Núcleo cooperativo de texto
155
- # As máscaras são derivadas internamente do pad_idx, mas se o usuário
156
- # fornecer attn_mask_a/attn_mask_b explicitamente, elas têm precedência
157
- # e são usadas para ignorar tokens de padding no cálculo de atenção.
158
- text_out = self.text_core(input_ids_a, input_ids_b, return_sequences=(mode == "generate"))
159
- fused_text = text_out["fused"] # [B, 256]
160
-
161
- # Sobrescrever máscaras se fornecidas externamente (para uso downstream)
162
- if attn_mask_a is not None:
163
- text_out["mask_A"] = attn_mask_a.to(text_out["mask_A"].device).float()
164
- if attn_mask_b is not None:
165
- text_out["mask_B"] = attn_mask_b.to(text_out["mask_B"].device).float()
166
-
167
- # 2. Imagem
168
- if images is not None:
169
- img_seq = self.image_encoder(images) # [B, T_img, D_img]
170
- img_feat = img_seq.mean(dim=1) # [B, D_img]
171
- img_feat = self.img_proj(img_feat)
172
- else:
173
- # Vetor zero se não houver modalidade
174
- img_feat = torch.zeros(
175
- fused_text.size(0), self.img_proj.out_features,
176
- device=fused_text.device, dtype=fused_text.dtype,
177
- )
178
-
179
- # 3. Áudio
180
- if audios is not None:
181
- aud_seq = self.audio_encoder(audios) # [B, T_aud, D_aud]
182
- aud_feat = aud_seq.mean(dim=1)
183
- aud_feat = self.aud_proj(aud_feat)
184
- else:
185
- aud_feat = torch.zeros(
186
- fused_text.size(0), self.aud_proj.out_features,
187
- device=fused_text.device, dtype=fused_text.dtype,
188
- )
189
-
190
- # 4. Fusão multimodal
191
- fused = self.fusion(fused_text, img_feat, aud_feat) # [B, fusion_dim]
192
- fused = self.dropout(fused)
193
-
194
- result = {
195
- "fused": fused,
196
- "text_out": text_out,
197
- "img_feat": img_feat,
198
- "aud_feat": aud_feat,
199
- }
200
-
201
- # 5. Cabeçalhos
202
- if mode == "classify":
203
- result["logits"] = self.classifier(fused) # [B, num_classes]
204
- elif mode == "generate":
205
- # Modo geração: projetar fusão para vocab_size
206
- # (modo simplificado: geração single-step baseada na fusão)
207
- result["logits"] = self.lm_head(fused) # [B, vocab_size]
208
- else:
209
- raise ValueError(f"mode deve ser 'classify' ou 'generate', recebeu '{mode}'")
210
-
211
- return result
212
-
213
-
214
- __all__ = ["MultimodalCNNBiGRU"]