mrj-crom commited on
Commit
68e00e6
·
verified ·
1 Parent(s): d53fb8d

sync: datasets/gerar_dataset_comprimido.py

Browse files
Files changed (1) hide show
  1. datasets/gerar_dataset_comprimido.py +444 -0
datasets/gerar_dataset_comprimido.py ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ╔══════════════════════════════════════════════════════════════╗
4
+ ║ 🧬 CROM-IA V2: Gerador de Datasets Comprimidos via DNA ║
5
+ ║ ║
6
+ ║ Comprime respostas do Alpaca-PT usando codebooks semânticos ║
7
+ ║ Modo fixo: codebook estático ║
8
+ ║ Modo dinâmico: codebook que expande com escapes frequentes ║
9
+ ╚══════════════════════════════════════════════════════════════╝
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import json
15
+ import re
16
+ import math
17
+ import argparse
18
+ from collections import Counter
19
+
20
+
21
+ def carregar_codebook(caminho):
22
+ """Carrega um codebook JSON."""
23
+ with open(caminho, 'r', encoding='utf-8') as f:
24
+ return json.load(f)
25
+
26
+
27
+ def carregar_corpus():
28
+ """Carrega corpus Alpaca-PT do HuggingFace."""
29
+ print(" [1] 📥 Carregando corpus Alpaca-PT...")
30
+ try:
31
+ from datasets import load_dataset
32
+ dataset = load_dataset(
33
+ "FreedomIntelligence/alpaca-gpt4-portuguese",
34
+ split="train"
35
+ )
36
+ print(f" ✅ {len(dataset)} exemplos carregados")
37
+ return dataset
38
+ except Exception as e:
39
+ print(f" ❌ Erro: {e}")
40
+ sys.exit(1)
41
+
42
+
43
+ def tokenizar(texto):
44
+ """Tokeniza texto em palavras."""
45
+ return re.findall(r'[\w]+|[.,!?;:]', texto.lower())
46
+
47
+
48
+ def comprimir_texto(texto, reverse_map, escape_prefix="@@"):
49
+ """
50
+ Comprime texto usando o codebook (greedy longest match).
51
+
52
+ Retorna:
53
+ compressed: string de códigos DNA separados por espaço
54
+ stats: dicionário com métricas de compressão
55
+ """
56
+ palavras = tokenizar(texto)
57
+ if not palavras:
58
+ return "", {"hits": 0, "misses": 0, "total_words": 0}
59
+
60
+ resultado = []
61
+ escapes = []
62
+ i = 0
63
+ hits = 0
64
+ misses = 0
65
+
66
+ while i < len(palavras):
67
+ matched = False
68
+
69
+ # Tentar match do maior fragmento primeiro (greedy)
70
+ max_n = min(20, len(palavras) - i)
71
+ for n in range(max_n, 0, -1):
72
+ fragmento = ' '.join(palavras[i:i+n])
73
+ if fragmento in reverse_map:
74
+ resultado.append(reverse_map[fragmento])
75
+ hits += n # Cada palavra do fragmento conta como hit
76
+ i += n
77
+ matched = True
78
+ break
79
+
80
+ if not matched:
81
+ # Escape: emitir texto literal
82
+ palavra = palavras[i]
83
+ resultado.append(f"{escape_prefix}{palavra}")
84
+ escapes.append(palavra)
85
+ misses += 1
86
+ i += 1
87
+
88
+ compressed = ' '.join(resultado)
89
+ total = hits + misses
90
+
91
+ stats = {
92
+ "hits": hits,
93
+ "misses": misses,
94
+ "total_words": total,
95
+ "hit_rate": round(hits / max(total, 1) * 100, 1),
96
+ "tokens_dna": len(resultado),
97
+ "palavras_originais": len(palavras),
98
+ "taxa_real": round(len(palavras) / max(len(resultado), 1), 2),
99
+ "escapes": escapes,
100
+ }
101
+
102
+ return compressed, stats
103
+
104
+
105
+ def expandir_codebook_dinamico(codebook, contagem_escapes, limiar):
106
+ """
107
+ Expande codebook dinâmico com escapes frequentes.
108
+
109
+ Adiciona fragmentos que apareceram como escape mais que `limiar` vezes.
110
+ """
111
+ from itertools import product
112
+
113
+ reverse_map = codebook.get("reverse_map", {})
114
+ entries = codebook.get("entries", {})
115
+
116
+ # Encontrar próximo código disponível
117
+ max_len = max((len(k) for k in entries.keys()), default=2)
118
+ codigos_usados = set(entries.keys())
119
+
120
+ adicionados = 0
121
+ for fragmento, freq in contagem_escapes.most_common():
122
+ if freq < limiar:
123
+ break
124
+ if fragmento in reverse_map:
125
+ continue
126
+ if len(fragmento) < 2 or not any(c.isalpha() for c in fragmento):
127
+ continue
128
+
129
+ # Gerar próximo código disponível
130
+ codigo = None
131
+ for length in range(2, max_len + 3):
132
+ for combo in product(['A', 'T', 'C', 'G'], repeat=length):
133
+ c = ''.join(combo)
134
+ if c not in codigos_usados:
135
+ codigo = c
136
+ codigos_usados.add(c)
137
+ break
138
+ if codigo:
139
+ break
140
+
141
+ if codigo:
142
+ entries[codigo] = {
143
+ "text": fragmento,
144
+ "freq": freq,
145
+ "category": "dynamic",
146
+ "n": len(fragmento.split()),
147
+ }
148
+ reverse_map[fragmento] = codigo
149
+ adicionados += 1
150
+
151
+ codebook["entries"] = entries
152
+ codebook["reverse_map"] = reverse_map
153
+ codebook["stats"]["dynamic_additions"] = adicionados
154
+ codebook["stats"]["total_entries"] = len(entries)
155
+
156
+ return codebook, adicionados
157
+
158
+
159
+ def gerar_dataset(codebook, dataset_corpus, output_path, max_amostras=10000):
160
+ """
161
+ Gera dataset comprimido no formato trifásico.
162
+
163
+ 33% Humano → DNA (codificação)
164
+ 33% DNA → Humano (decodificação)
165
+ 33% Contexto misto
166
+ """
167
+ reverse_map = codebook.get("reverse_map", {})
168
+ entries = codebook.get("entries", {})
169
+ escape_prefix = codebook.get("escape_prefix", "@@")
170
+ taxa = codebook.get("taxa_alvo", "1:?")
171
+ modo = codebook.get("modo", "fixo")
172
+ is_dynamic = codebook.get("dynamic", False)
173
+ limiar = codebook.get("expansion_threshold", 10)
174
+
175
+ TERCO = max_amostras // 3
176
+
177
+ print(f" [2] 🏗️ Gerando dataset ({taxa}, {modo})...")
178
+ print(f" Fase A: {TERCO} Humano → DNA")
179
+ print(f" Fase B: {TERCO} DNA → Humano")
180
+ print(f" Fase C: {max_amostras - 2*TERCO} Contexto Misto")
181
+
182
+ dataset_corpus = dataset_corpus.shuffle(seed=42)
183
+ dados = []
184
+ stats_global = {
185
+ "hits_total": 0,
186
+ "misses_total": 0,
187
+ "descartados": 0,
188
+ "taxa_compressao_media": [],
189
+ "hit_rates": [],
190
+ }
191
+ contagem_escapes = Counter()
192
+
193
+ for item in dataset_corpus:
194
+ if len(dados) >= max_amostras:
195
+ break
196
+
197
+ convs = item.get('conversations', [])
198
+ if not convs or len(convs) < 2:
199
+ stats_global["descartados"] += 1
200
+ continue
201
+
202
+ instrucao = ""
203
+ saida = ""
204
+ for c in convs:
205
+ if c.get("from") == "human" and not instrucao:
206
+ instrucao = str(c.get("value", "")).replace("\n", " ").strip()
207
+ elif c.get("from") == "gpt" and not saida:
208
+ saida = str(c.get("value", "")).replace("\n", " ").strip()
209
+
210
+ if not instrucao or not saida or len(instrucao) + len(saida) > 500:
211
+ stats_global["descartados"] += 1
212
+ continue
213
+
214
+ # Comprimir a saída usando o codebook
215
+ compressed, comp_stats = comprimir_texto(saida, reverse_map, escape_prefix)
216
+
217
+ if not compressed:
218
+ stats_global["descartados"] += 1
219
+ continue
220
+
221
+ stats_global["hits_total"] += comp_stats["hits"]
222
+ stats_global["misses_total"] += comp_stats["misses"]
223
+ stats_global["taxa_compressao_media"].append(comp_stats["taxa_real"])
224
+ stats_global["hit_rates"].append(comp_stats["hit_rate"])
225
+
226
+ # Contar escapes para expansão dinâmica
227
+ for esc in comp_stats["escapes"]:
228
+ contagem_escapes[esc] += 1
229
+
230
+ fase = len(dados)
231
+
232
+ # FASE A: Humano pergunta → IA responde em DNA comprimido
233
+ if fase < TERCO:
234
+ obj = {
235
+ "instruction": f"Você é um compressor CROM DNA (taxa {taxa}). Comprima a resposta usando códigos do codebook semântico DNA. Use prefixo {escape_prefix} para palavras sem código.",
236
+ "input": instrucao,
237
+ "output": compressed,
238
+ }
239
+
240
+ # FASE B: DNA comprimido → IA decodifica para humano
241
+ elif fase < 2 * TERCO:
242
+ obj = {
243
+ "instruction": f"Decodifique os códigos DNA CROM (codebook semântico {taxa}) para linguagem humana em Português.",
244
+ "input": compressed,
245
+ "output": saida,
246
+ }
247
+
248
+ # FASE C: Contexto misto
249
+ else:
250
+ snippet_compressed, _ = comprimir_texto(
251
+ saida[:80], reverse_map, escape_prefix
252
+ )
253
+ obj = {
254
+ "instruction": f"Analise o contexto abaixo. O DNA CROM ({taxa}) representa texto comprimido. Responda em Português claro.",
255
+ "input": f"Contexto: {instrucao}\nDNA: {snippet_compressed}",
256
+ "output": saida,
257
+ }
258
+
259
+ dados.append(obj)
260
+
261
+ # ── Expansão dinâmica (se modo dinâmico) ──
262
+ if is_dynamic and contagem_escapes:
263
+ print(f" [3] 🔄 Expansão dinâmica do codebook...")
264
+ codebook, adicionados = expandir_codebook_dinamico(
265
+ codebook, contagem_escapes, limiar
266
+ )
267
+ print(f" ✅ {adicionados} novos códigos adicionados")
268
+
269
+ if adicionados > 0:
270
+ print(f" [3b] 🔄 Re-comprimindo dataset com codebook expandido...")
271
+ reverse_map = codebook["reverse_map"]
272
+ dados_recomprimidos = []
273
+
274
+ for i, obj in enumerate(dados):
275
+ fase = i
276
+ # Re-comprimir apenas Fase A
277
+ if fase < TERCO and escape_prefix in obj["output"]:
278
+ # Re-comprimir
279
+ # Precisamos da saída original, que está na Fase B correspondente
280
+ # Como workaround, re-usamos o texto da instrução
281
+ pass # Mantém como está na primeira passada
282
+ dados_recomprimidos.append(obj)
283
+
284
+ dados = dados_recomprimidos
285
+
286
+ # ── Salvar dataset ──
287
+ print(f" [4] 💾 Salvando {len(dados)} amostras em {output_path}...")
288
+ with open(output_path, 'w', encoding='utf-8') as f:
289
+ for obj in dados:
290
+ f.write(json.dumps(obj, ensure_ascii=False) + "\n")
291
+
292
+ tamanho_mb = os.path.getsize(output_path) / 1024 / 1024
293
+
294
+ # ── Estatísticas finais ──
295
+ avg_taxa = (sum(stats_global["taxa_compressao_media"]) /
296
+ max(len(stats_global["taxa_compressao_media"]), 1))
297
+ avg_hit = (sum(stats_global["hit_rates"]) /
298
+ max(len(stats_global["hit_rates"]), 1))
299
+
300
+ print(f"\n {'─' * 50}")
301
+ print(f" 📊 ESTATÍSTICAS DO DATASET ({taxa}, {modo})")
302
+ print(f" {'─' * 50}")
303
+ print(f" Amostras : {len(dados)}")
304
+ print(f" Arquivo : {tamanho_mb:.1f} MB")
305
+ print(f" Taxa compressão : 1:{avg_taxa:.1f}")
306
+ print(f" Hit Rate médio : {avg_hit:.1f}%")
307
+ print(f" Hits totais : {stats_global['hits_total']}")
308
+ print(f" Misses totais : {stats_global['misses_total']}")
309
+ print(f" Descartados : {stats_global['descartados']}")
310
+ if is_dynamic:
311
+ print(f" Códigos dinâmicos: {codebook['stats'].get('dynamic_additions', 0)}")
312
+ print(f" {'─' * 50}")
313
+
314
+ return codebook, stats_global
315
+
316
+
317
+ def main():
318
+ parser = argparse.ArgumentParser(
319
+ description="🧬 CROM-IA V2: Gerador de Datasets Comprimidos via DNA"
320
+ )
321
+ parser.add_argument(
322
+ "--codebook-dir", type=str,
323
+ default=None,
324
+ help="Diretório com os codebooks JSON"
325
+ )
326
+ parser.add_argument(
327
+ "--output-dir", type=str,
328
+ default=None,
329
+ help="Diretório de saída para datasets"
330
+ )
331
+ parser.add_argument(
332
+ "--taxas", nargs="+",
333
+ default=["1x3", "1x5", "1x10", "1x20"],
334
+ choices=["1x3", "1x5", "1x10", "1x20"],
335
+ help="Taxas a processar"
336
+ )
337
+ parser.add_argument(
338
+ "--modos", nargs="+",
339
+ default=["fixo", "dinamico"],
340
+ choices=["fixo", "dinamico"],
341
+ help="Modos a processar"
342
+ )
343
+ parser.add_argument(
344
+ "--amostras", type=int, default=10000,
345
+ help="Amostras por dataset (default: 10000)"
346
+ )
347
+ args = parser.parse_args()
348
+
349
+ base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
350
+
351
+ if args.codebook_dir is None:
352
+ args.codebook_dir = os.path.join(base_dir, "codebooks")
353
+ if args.output_dir is None:
354
+ args.output_dir = os.path.join(base_dir, "datasets")
355
+
356
+ os.makedirs(args.output_dir, exist_ok=True)
357
+
358
+ print("=" * 60)
359
+ print(" 🧬 CROM-IA V2: GERADOR DE DATASETS COMPRIMIDOS DNA")
360
+ print("=" * 60)
361
+ print(f" Codebooks : {args.codebook_dir}")
362
+ print(f" Saída : {args.output_dir}")
363
+ print(f" Taxas : {args.taxas}")
364
+ print(f" Modos : {args.modos}")
365
+ print(f" Amostras : {args.amostras}")
366
+ print("=" * 60)
367
+
368
+ # Carregar corpus uma vez
369
+ dataset_corpus = carregar_corpus()
370
+
371
+ resultados = []
372
+
373
+ for taxa in args.taxas:
374
+ for modo in args.modos:
375
+ codebook_path = os.path.join(
376
+ args.codebook_dir, f"codebook_{taxa}_{modo}.json"
377
+ )
378
+
379
+ if not os.path.exists(codebook_path):
380
+ print(f"\n⚠️ Codebook não encontrado: {codebook_path}")
381
+ print(f" Execute gerar_codebook.py primeiro!")
382
+ continue
383
+
384
+ print(f"\n{'═' * 60}")
385
+ print(f" Processando: {taxa} / {modo}")
386
+ print(f"{'═' * 60}")
387
+
388
+ codebook = carregar_codebook(codebook_path)
389
+
390
+ output_path = os.path.join(
391
+ args.output_dir, f"dataset_dna_{taxa}_{modo}.jsonl"
392
+ )
393
+
394
+ codebook_atualizado, stats = gerar_dataset(
395
+ codebook, dataset_corpus, output_path, args.amostras
396
+ )
397
+
398
+ # Se dinâmico, salvar codebook atualizado
399
+ if modo == "dinamico" and codebook_atualizado.get("stats", {}).get("dynamic_additions", 0) > 0:
400
+ salvar_path = codebook_path.replace(".json", "_expandido.json")
401
+ with open(salvar_path, 'w', encoding='utf-8') as f:
402
+ json.dump(codebook_atualizado, f, ensure_ascii=False, indent=2)
403
+ print(f" 💾 Codebook dinâmico expandido salvo: {salvar_path}")
404
+
405
+ avg_taxa_real = (
406
+ sum(stats["taxa_compressao_media"]) /
407
+ max(len(stats["taxa_compressao_media"]), 1)
408
+ )
409
+ avg_hit = (
410
+ sum(stats["hit_rates"]) /
411
+ max(len(stats["hit_rates"]), 1)
412
+ )
413
+
414
+ resultados.append({
415
+ "taxa": taxa,
416
+ "modo": modo,
417
+ "amostras": args.amostras,
418
+ "taxa_real": round(avg_taxa_real, 2),
419
+ "hit_rate": round(avg_hit, 1),
420
+ })
421
+
422
+ # ── Relatório Final Comparativo ──
423
+ print("\n" + "=" * 70)
424
+ print(" 📊 RELATÓRIO COMPARATIVO — DATASETS GERADOS")
425
+ print("=" * 70)
426
+ print(f"{'Taxa':<8} {'Modo':<12} {'Amostras':<10} {'Compressão':<12} {'Hit Rate':<10}")
427
+ print("─" * 70)
428
+
429
+ for r in resultados:
430
+ print(
431
+ f"{r['taxa']:<8} {r['modo']:<12} "
432
+ f"{r['amostras']:<10} "
433
+ f"1:{r['taxa_real']:<11} "
434
+ f"{r['hit_rate']:.1f}%"
435
+ )
436
+
437
+ print("─" * 70)
438
+ print(f" Total: {len(resultados)} datasets gerados em {args.output_dir}")
439
+ print("=" * 70)
440
+ print("\n🚀 Próximo passo: Execute treinar_codebook.py no Google Colab!")
441
+
442
+
443
+ if __name__ == "__main__":
444
+ main()