PowerMachine commited on
Commit
4da4189
·
verified ·
1 Parent(s): 406ea50

Upload cnn_bigru/data/streaming_dataset.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/data/streaming_dataset.py +308 -0
cnn_bigru/data/streaming_dataset.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """streaming_dataset.py — Carregador de dataset streaming para o CNN-BiGRU.
2
+
3
+ Adaptado de xavante_work/flexnet/streaming_datasets_v13_9.py, com:
4
+ - Modo streaming (IterableDataset, sem materialização completa)
5
+ - Fallback sintético quando datasets externos estão indisponíveis
6
+ - Suporte multimodal: texto + imagem (placeholder) + áudio (placeholder)
7
+ - Garantia de produzir N amostras para o teste de 50 samples
8
+
9
+ Para o projeto CNN-BiGRU, usamos um gerador sintético multi-modal padrão
10
+ quando a rede ou HF não está acessível, garantindo que o teste de 50 amostras
11
+ sempre execute.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import os
17
+ import random
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Dict, Iterator, List, Optional, Sequence
20
+
21
+ import numpy as np
22
+ import torch
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class MultimodalSample:
29
+ """Amostra multimodal: texto + imagem (HxWxC float) + áudio (spec TxF)."""
30
+ sample_id: int
31
+ text_a: str # Stream A (e.g. pergunta/título)
32
+ text_b: str # Stream B (e.g. contexto/corpo)
33
+ image: Optional[np.ndarray] = None # [H, W, C] float32 in [0,1]
34
+ audio: Optional[np.ndarray] = None # [T, F] spectrogram float32
35
+ label: int = 0
36
+ metadata: Dict[str, Any] = field(default_factory=dict)
37
+
38
+
39
+ # Dataset padrão V13.9.1 (mesmos do reference streaming_datasets_v13_9.py)
40
+ DEFAULT_DATASETS = [
41
+ "CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1",
42
+ "Madras1/corpus-ptbr-v2",
43
+ "rhaymison/multmodal_175k_portuguese",
44
+ "TucanoBR/GigaVerbo",
45
+ "nvidia/OpenMathReasoning",
46
+ "MathLLMs/MathVision",
47
+ "nvidia/OpenMathInstruct-2",
48
+ "dominguesm/restore-punctuation-ptbr-dataset",
49
+ "carolina-c4ai/corpus-carolina",
50
+ ]
51
+
52
+
53
+ def _extract_field(sample: Dict[str, Any], candidates: List[str]) -> Optional[str]:
54
+ for f in candidates:
55
+ if f in sample:
56
+ v = sample[f]
57
+ if isinstance(v, str) and v.strip():
58
+ return v
59
+ if isinstance(v, list):
60
+ parts = []
61
+ for m in v:
62
+ if isinstance(m, dict):
63
+ c = m.get("content", "")
64
+ if isinstance(c, str) and c.strip():
65
+ parts.append(c)
66
+ elif isinstance(m, str) and m.strip():
67
+ parts.append(m)
68
+ if parts:
69
+ return "\n".join(parts)
70
+ return None
71
+
72
+
73
+ def _try_load_hf_streaming(
74
+ dataset_name: str,
75
+ split: str = "train",
76
+ hf_token: Optional[str] = None,
77
+ ):
78
+ """Tenta carregar dataset HF em streaming. Retorna None se falhar."""
79
+ try:
80
+ from datasets import load_dataset
81
+ ds = load_dataset(dataset_name, split=split, streaming=True, token=hf_token)
82
+ logger.info("Streaming OK: %s[%s]", dataset_name, split)
83
+ return ds
84
+ except Exception as e:
85
+ logger.info("Streaming falhou para %s: %s", dataset_name, str(e)[:120])
86
+ return None
87
+
88
+
89
+ def synthetic_multimodal_stream(
90
+ n_samples: int,
91
+ seed: int = 42,
92
+ image_size: tuple = (28, 28, 1),
93
+ audio_shape: tuple = (64, 40),
94
+ vocab_texts: Optional[Sequence[str]] = None,
95
+ ) -> Iterator[MultimodalSample]:
96
+ """Gera amostras sintéticas multimodais determinísticas.
97
+
98
+ Usado quando o dataset HF está indisponível (offline, rate-limit, etc.).
99
+ Garante que o teste de 50 amostras sempre execute.
100
+ """
101
+ rng = random.Random(seed)
102
+ np_rng = np.random.RandomState(seed)
103
+
104
+ if vocab_texts is None:
105
+ vocab_texts = [
106
+ "o modelo aprende padrões locais com convoluções",
107
+ "a ponte de cooperação troca informações entre fluxos",
108
+ "grus capturam dependências temporais bidirecionais",
109
+ "atenção cruzada reduz perplexidade em textos longos",
110
+ "a fusão multimodal combina texto imagem e áudio",
111
+ "penalidades evitam repetições viciosas na geração",
112
+ "a camada anti-alucinação usa lógica fuzzy de lukasiewicz",
113
+ "o verificador classifica passos com sigmoid binária",
114
+ "ajustes dinâmicos de lr controlam explosão de gradiente",
115
+ "normalização espectral estabiliza o treinamento",
116
+ ]
117
+
118
+ n_vocab = len(vocab_texts)
119
+ for i in range(n_samples):
120
+ text_a = vocab_texts[rng.randrange(n_vocab)]
121
+ text_b = vocab_texts[rng.randrange(n_vocab)]
122
+ # Imagem sintética: padrões estruturados simples
123
+ img = np_rng.rand(*image_size).astype(np.float32)
124
+ # Adiciona um padrão que depende do índice (para discriminação)
125
+ img[i % image_size[0], :, :] = 1.0
126
+ # Áudio sintético: espectrograma
127
+ aud = np_rng.rand(*audio_shape).astype(np.float32) * 0.5
128
+ # Espectro com picos determinísticos
129
+ aud[i % audio_shape[0], :] += 0.5
130
+ yield MultimodalSample(
131
+ sample_id=i,
132
+ text_a=text_a,
133
+ text_b=text_b,
134
+ image=img,
135
+ audio=aud,
136
+ label=i % 3,
137
+ metadata={"source": "synthetic", "idx": i},
138
+ )
139
+
140
+
141
+ class MultimodalStreamingDataset(torch.utils.data.IterableDataset):
142
+ """Dataset streaming multimodal. Suporta HF + fallback sintético.
143
+
144
+ Args:
145
+ n_samples: número total de amostras a produzir.
146
+ hf_datasets: lista de datasets HF para tentar (em ordem).
147
+ hf_token: token HF (será limpo após uso).
148
+ use_synthetic_fallback: se True, usa sintético quando HF falha.
149
+ seed: seed para reprodutibilidade.
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ n_samples: int = 50,
155
+ hf_datasets: Optional[List[str]] = None,
156
+ hf_token: Optional[str] = None,
157
+ use_synthetic_fallback: bool = True,
158
+ seed: int = 42,
159
+ image_size: tuple = (28, 28, 1),
160
+ audio_shape: tuple = (64, 40),
161
+ ):
162
+ super().__init__()
163
+ self.n_samples = n_samples
164
+ self.hf_datasets = hf_datasets or DEFAULT_DATASETS
165
+ self.hf_token = hf_token
166
+ self.use_synthetic_fallback = use_synthetic_fallback
167
+ self.seed = seed
168
+ self.image_size = image_size
169
+ self.audio_shape = audio_shape
170
+
171
+ def _try_hf(self) -> Iterator[Dict[str, Any]]:
172
+ """Tenta carregar amostras HF. Retorna iterator vazio se falhar."""
173
+ for ds_name in self.hf_datasets:
174
+ ds = _try_load_hf_streaming(ds_name, split="train", hf_token=self.hf_token)
175
+ if ds is None:
176
+ continue
177
+ count = 0
178
+ text_candidates = ["text", "content", "question", "problem", "input",
179
+ "conversa", "description", "prompt", "instruction"]
180
+ label_candidates = ["answer", "response", "output", "solution",
181
+ "punctuated", "restored"]
182
+ for raw in ds:
183
+ if count >= self.n_samples:
184
+ break
185
+ try:
186
+ text_a = _extract_field(raw, text_candidates) or ""
187
+ text_b = _extract_field(raw, label_candidates) or ""
188
+ if len(text_a) < 5:
189
+ text_a = "pergunta de exemplo sobre o tema"
190
+ if len(text_b) < 5:
191
+ text_b = "resposta de exemplo para contexto"
192
+ # Image/audio placeholder: geramos sintéticos para manter multimodal
193
+ img = np.random.rand(*self.image_size).astype(np.float32) * 0.5
194
+ aud = np.random.rand(*self.audio_shape).astype(np.float32) * 0.5
195
+ yield {
196
+ "sample_id": count,
197
+ "text_a": text_a[:200], # truncado para teste
198
+ "text_b": text_b[:200],
199
+ "image": img,
200
+ "audio": aud,
201
+ "label": count % 3,
202
+ "metadata": {"source": ds_name, "idx": count},
203
+ }
204
+ count += 1
205
+ except Exception as e:
206
+ logger.debug("skip sample %d from %s: %s", count, ds_name, e)
207
+ continue
208
+ if count > 0:
209
+ logger.info("HF yield %d samples de %s", count, ds_name)
210
+ return
211
+
212
+ def __iter__(self) -> Iterator[Dict[str, Any]]:
213
+ # Tentar HF primeiro
214
+ if self.hf_datasets:
215
+ try:
216
+ yield from self._try_hf()
217
+ return
218
+ except Exception as e:
219
+ logger.warning("HF streaming falhou (%s) — usando fallback sintético", e)
220
+
221
+ # Fallback sintético
222
+ if self.use_synthetic_fallback:
223
+ for s in synthetic_multimodal_stream(
224
+ n_samples=self.n_samples,
225
+ seed=self.seed,
226
+ image_size=self.image_size,
227
+ audio_shape=self.audio_shape,
228
+ ):
229
+ yield {
230
+ "sample_id": s.sample_id,
231
+ "text_a": s.text_a,
232
+ "text_b": s.text_b,
233
+ "image": s.image,
234
+ "audio": s.audio,
235
+ "label": s.label,
236
+ "metadata": s.metadata,
237
+ }
238
+
239
+
240
+ def collate_multimodal(
241
+ batch: List[Dict[str, Any]],
242
+ tokenizer,
243
+ max_len: int = 64,
244
+ ) -> Dict[str, torch.Tensor]:
245
+ """Cola um batch de amostras multimodais em tensores.
246
+
247
+ Returns dict com:
248
+ input_ids_a: [B, T] (stream A)
249
+ input_ids_b: [B, T] (stream B)
250
+ attn_mask_a: [B, T]
251
+ attn_mask_b: [B, T]
252
+ images: [B, C, H, W]
253
+ audios: [B, 1, T, F]
254
+ labels: [B]
255
+ """
256
+ texts_a = [b["text_a"] for b in batch]
257
+ texts_b = [b["text_b"] for b in batch]
258
+ ids_a = tokenizer.encode_batch(texts_a, add_special=True)
259
+ ids_b = tokenizer.encode_batch(texts_b, add_special=True)
260
+
261
+ pad_id = tokenizer.pad_id
262
+
263
+ def _pad(seqs, max_len):
264
+ out = []
265
+ masks = []
266
+ for s in seqs:
267
+ s = s[:max_len]
268
+ n = len(s)
269
+ padded = s + [pad_id] * (max_len - n)
270
+ mask = [1] * n + [0] * (max_len - n)
271
+ out.append(padded)
272
+ masks.append(mask)
273
+ return out, masks
274
+
275
+ padded_a, masks_a = _pad(ids_a, max_len)
276
+ padded_b, masks_b = _pad(ids_b, max_len)
277
+
278
+ images = np.stack([b["image"] for b in batch]) # [B, H, W, C]
279
+ # converte para [B, C, H, W]
280
+ if images.ndim == 4:
281
+ images = np.transpose(images, (0, 3, 1, 2))
282
+ else:
283
+ images = images[:, None, :, :] # add channel dim
284
+
285
+ audios = np.stack([b["audio"] for b in batch]) # [B, T, F]
286
+ audios = audios[:, None, :, :] # [B, 1, T, F]
287
+
288
+ labels = np.array([b["label"] for b in batch], dtype=np.int64)
289
+
290
+ return {
291
+ "input_ids_a": torch.tensor(padded_a, dtype=torch.long),
292
+ "input_ids_b": torch.tensor(padded_b, dtype=torch.long),
293
+ "attn_mask_a": torch.tensor(masks_a, dtype=torch.float),
294
+ "attn_mask_b": torch.tensor(masks_b, dtype=torch.float),
295
+ "images": torch.tensor(images, dtype=torch.float32),
296
+ "audios": torch.tensor(audios, dtype=torch.float32),
297
+ "labels": torch.tensor(labels, dtype=torch.long),
298
+ "sample_ids": [b["sample_id"] for b in batch],
299
+ }
300
+
301
+
302
+ __all__ = [
303
+ "MultimodalSample",
304
+ "MultimodalStreamingDataset",
305
+ "collate_multimodal",
306
+ "DEFAULT_DATASETS",
307
+ "synthetic_multimodal_stream",
308
+ ]