Delete cnn_bigru/models/multimodal_model.py with huggingface_hub
Browse files
cnn_bigru/models/multimodal_model.py
DELETED
|
@@ -1,183 +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 |
-
):
|
| 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"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|