PowerMachine commited on
Commit
ee2e50a
·
verified ·
1 Parent(s): 5914924

Upload cnn_bigru/models/multimodal_model.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/models/multimodal_model.py +183 -0
cnn_bigru/models/multimodal_model.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ):
60
+ super().__init__()
61
+ self.pad_idx = pad_idx
62
+ self.vocab_size = vocab_size
63
+ self.num_classes = num_classes
64
+
65
+ # Núcleo cooperativo de texto
66
+ self.text_core = CooperativeCNNBiGRU(
67
+ vocab_size=vocab_size,
68
+ embedding_dim=embedding_dim,
69
+ cnn_filters=cnn_filters,
70
+ gru_hidden=gru_hidden,
71
+ n_heads=n_heads,
72
+ dropout=dropout,
73
+ pad_idx=pad_idx,
74
+ use_spectral_norm=use_spectral_norm,
75
+ )
76
+ d_text = self.text_core.feat_dim * 2 # 256 (fused A+B = 256, *2 para foco)
77
+
78
+ # Encoders multimodais
79
+ self.image_encoder = ImageEncoder(
80
+ in_channels=img_channels,
81
+ out_dim=img_out_dim,
82
+ hidden_dim=img_hidden,
83
+ )
84
+ self.audio_encoder = AudioEncoder(
85
+ n_freq=audio_freq,
86
+ out_dim=audio_out_dim,
87
+ hidden_dim=audio_hidden,
88
+ )
89
+
90
+ # Pooling de imagem/áudio para vetor fixo
91
+ self.img_proj = nn.Linear(img_out_dim, img_out_dim)
92
+ self.aud_proj = nn.Linear(audio_out_dim, audio_out_dim)
93
+
94
+ # Fusão multimodal
95
+ self.fusion = MultimodalFusion(
96
+ d_text=d_text,
97
+ d_img=img_out_dim,
98
+ d_aud=audio_out_dim,
99
+ out_dim=fusion_dim,
100
+ dropout=dropout,
101
+ )
102
+
103
+ # Cabeçalho de classificação
104
+ self.classifier = nn.Linear(fusion_dim, num_classes)
105
+
106
+ # Cabeçalho de geração (LM head) — para modo autoregressivo
107
+ self.lm_head = nn.Linear(fusion_dim, vocab_size, bias=False)
108
+
109
+ # Dropout final
110
+ self.dropout = nn.Dropout(dropout)
111
+
112
+ def forward(
113
+ self,
114
+ input_ids_a: torch.Tensor,
115
+ input_ids_b: torch.Tensor,
116
+ images: Optional[torch.Tensor] = None,
117
+ audios: Optional[torch.Tensor] = None,
118
+ attn_mask_a: Optional[torch.Tensor] = None,
119
+ attn_mask_b: Optional[torch.Tensor] = None,
120
+ mode: str = "classify",
121
+ ) -> dict:
122
+ """
123
+ Args:
124
+ input_ids_a, input_ids_b: [B, T] tokens dos streams A e B
125
+ images: [B, C, H, W] ou None
126
+ audios: [B, 1, T, F] ou None
127
+ mode: "classify" | "generate"
128
+
129
+ Returns:
130
+ dict com logits (classificação ou geração) + features intermediárias
131
+ """
132
+ # 1. Núcleo cooperativo de texto
133
+ text_out = self.text_core(input_ids_a, input_ids_b, return_sequences=(mode == "generate"))
134
+ fused_text = text_out["fused"] # [B, 256]
135
+
136
+ # 2. Imagem
137
+ if images is not None:
138
+ img_seq = self.image_encoder(images) # [B, T_img, D_img]
139
+ img_feat = img_seq.mean(dim=1) # [B, D_img]
140
+ img_feat = self.img_proj(img_feat)
141
+ else:
142
+ # Vetor zero se não houver modalidade
143
+ img_feat = torch.zeros(
144
+ fused_text.size(0), self.img_proj.out_features,
145
+ device=fused_text.device, dtype=fused_text.dtype,
146
+ )
147
+
148
+ # 3. Áudio
149
+ if audios is not None:
150
+ aud_seq = self.audio_encoder(audios) # [B, T_aud, D_aud]
151
+ aud_feat = aud_seq.mean(dim=1)
152
+ aud_feat = self.aud_proj(aud_feat)
153
+ else:
154
+ aud_feat = torch.zeros(
155
+ fused_text.size(0), self.aud_proj.out_features,
156
+ device=fused_text.device, dtype=fused_text.dtype,
157
+ )
158
+
159
+ # 4. Fusão multimodal
160
+ fused = self.fusion(fused_text, img_feat, aud_feat) # [B, fusion_dim]
161
+ fused = self.dropout(fused)
162
+
163
+ result = {
164
+ "fused": fused,
165
+ "text_out": text_out,
166
+ "img_feat": img_feat,
167
+ "aud_feat": aud_feat,
168
+ }
169
+
170
+ # 5. Cabeçalhos
171
+ if mode == "classify":
172
+ result["logits"] = self.classifier(fused) # [B, num_classes]
173
+ elif mode == "generate":
174
+ # Modo geração: projetar fusão para vocab_size
175
+ # (modo simplificado: geração single-step baseada na fusão)
176
+ result["logits"] = self.lm_head(fused) # [B, vocab_size]
177
+ else:
178
+ raise ValueError(f"mode deve ser 'classify' ou 'generate', recebeu '{mode}'")
179
+
180
+ return result
181
+
182
+
183
+ __all__ = ["MultimodalCNNBiGRU"]