PowerMachine commited on
Commit
d4b28f1
·
verified ·
1 Parent(s): 043bb18

Upload cnn_bigru/tokenizer/bbpe_tokenizer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/tokenizer/bbpe_tokenizer.py +173 -0
cnn_bigru/tokenizer/bbpe_tokenizer.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """bbpe_tokenizer.py — BBPE (Byte-Level BPE) Tokenizer simplificado.
2
+
3
+ Inspirado em PowerMachine/BiGRU_T_version/src/bigru_t/tokenizer/bbpe_tokenizer.py,
4
+ mas simplificado para o projeto CNN-BiGRU. Usa a biblioteca `tokenizers` da
5
+ HuggingFace com modelo BPE byte-level.
6
+
7
+ Cobertura universal UTF-8 (qualquer string é tokenizável sem <unk>).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import os
14
+ from pathlib import Path
15
+ from typing import List, Optional, Sequence
16
+
17
+ from tokenizers import Tokenizer
18
+ from tokenizers.models import BPE
19
+ from tokenizers.pre_tokenizers import ByteLevel
20
+ from tokenizers.processors import ByteLevel as ByteLevelProcessor
21
+ from tokenizers.decoders import ByteLevel as ByteLevelDecoder
22
+ from tokenizers.trainers import BpeTrainer
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ BOS_TOKEN = "<s>"
27
+ PAD_TOKEN = "<pad>"
28
+ EOS_TOKEN = "</s>"
29
+ UNK_TOKEN = "<unk>"
30
+
31
+ SPECIAL_TOKENS = [BOS_TOKEN, PAD_TOKEN, EOS_TOKEN, UNK_TOKEN]
32
+ BOS_ID = 0
33
+ PAD_ID = 1
34
+ EOS_ID = 2
35
+ UNK_ID = 3
36
+
37
+
38
+ class BBPETokenizer:
39
+ """Wrapper sobre Tokenizer (HuggingFace) com API conveniente.
40
+
41
+ Garante: dec(enc(s)) = s para todo s UTF-8.
42
+ """
43
+
44
+ def __init__(self, tokenizer: Optional[Tokenizer] = None):
45
+ self._tok = tokenizer
46
+
47
+ @property
48
+ def tokenizer(self) -> Tokenizer:
49
+ if self._tok is None:
50
+ raise RuntimeError("Tokenizer não inicializado. Use train_from_* ou load.")
51
+ return self._tok
52
+
53
+ @property
54
+ def vocab_size(self) -> int:
55
+ return self.tokenizer.get_vocab_size()
56
+
57
+ @property
58
+ def bos_id(self) -> int:
59
+ return self.tokenizer.token_to_id(BOS_TOKEN) or BOS_ID
60
+
61
+ @property
62
+ def pad_id(self) -> int:
63
+ return self.tokenizer.token_to_id(PAD_TOKEN) or PAD_ID
64
+
65
+ @property
66
+ def eos_id(self) -> int:
67
+ return self.tokenizer.token_to_id(EOS_TOKEN) or EOS_ID
68
+
69
+ @property
70
+ def unk_id(self) -> int:
71
+ return self.tokenizer.token_to_id(UNK_TOKEN) or UNK_ID
72
+
73
+ def encode(self, text: str, add_special: bool = True) -> List[int]:
74
+ if add_special:
75
+ text = f"{BOS_TOKEN}{text}{EOS_TOKEN}"
76
+ return self.tokenizer.encode(text).ids
77
+
78
+ def encode_batch(self, texts: Sequence[str], add_special: bool = True) -> List[List[int]]:
79
+ if add_special:
80
+ texts = [f"{BOS_TOKEN}{t}{EOS_TOKEN}" for t in texts]
81
+ return [e.ids for e in self.tokenizer.encode_batch(list(texts))]
82
+
83
+ def decode(self, ids: List[int], skip_special: bool = True) -> str:
84
+ if skip_special:
85
+ ids = [i for i in ids if i not in (self.bos_id, self.pad_id, self.eos_id, self.unk_id)]
86
+ return self.tokenizer.decode(ids)
87
+
88
+ def save(self, path: str) -> None:
89
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
90
+ self.tokenizer.save(path)
91
+ logger.info("Tokenizer salvo em %s", path)
92
+
93
+ @classmethod
94
+ def load(cls, path: str) -> "BBPETokenizer":
95
+ tok = Tokenizer.from_file(path)
96
+ return cls(tok)
97
+
98
+ @classmethod
99
+ def train_from_files(
100
+ cls,
101
+ files: Sequence[str],
102
+ vocab_size: int = 8000,
103
+ min_frequency: int = 2,
104
+ ) -> "BBPETokenizer":
105
+ # Desativa paralelismo de tokenizers para evitar problemas de GIL no shutdown
106
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
107
+ tokenizer = Tokenizer(BPE(unk_token=UNK_TOKEN))
108
+ tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=False)
109
+ trainer = BpeTrainer(
110
+ vocab_size=vocab_size,
111
+ min_frequency=min_frequency,
112
+ special_tokens=SPECIAL_TOKENS,
113
+ show_progress=False,
114
+ )
115
+ valid_files = [str(Path(p).resolve()) for p in files if Path(p).exists()]
116
+ if not valid_files:
117
+ raise FileNotFoundError(f"Nenhum arquivo válido em: {files}")
118
+ tokenizer.train(valid_files, trainer)
119
+ tokenizer.post_processor = ByteLevelProcessor(trim_offsets=False)
120
+ # Decoder byte-level: reverte a codificação byte-level para UTF-8
121
+ tokenizer.decoder = ByteLevelDecoder()
122
+ logger.info("BBPE treinado com vocab_size=%d", tokenizer.get_vocab_size())
123
+ return cls(tokenizer)
124
+
125
+ @classmethod
126
+ def train_from_texts(
127
+ cls,
128
+ texts: Sequence[str],
129
+ vocab_size: int = 8000,
130
+ min_frequency: int = 1,
131
+ ) -> "BBPETokenizer":
132
+ """Treina a partir de uma lista de textos em memória."""
133
+ # Salva temporariamente
134
+ import tempfile
135
+ with tempfile.NamedTemporaryFile(
136
+ mode="w", suffix=".txt", delete=False, encoding="utf-8"
137
+ ) as f:
138
+ for line in texts:
139
+ line = line.strip()
140
+ if line:
141
+ f.write(line + "\n")
142
+ tmp_path = f.name
143
+ try:
144
+ return cls.train_from_files([tmp_path], vocab_size=vocab_size,
145
+ min_frequency=min_frequency)
146
+ finally:
147
+ try:
148
+ os.unlink(tmp_path)
149
+ except OSError:
150
+ pass
151
+
152
+ def validate_roundtrip(self, test_texts: Sequence[str]) -> float:
153
+ """Valida que dec(enc(s)) == s. Retorna fração de sucessos."""
154
+ if not test_texts:
155
+ return 1.0
156
+ ok = 0
157
+ for s in test_texts:
158
+ try:
159
+ ids = self.encode(s, add_special=False)
160
+ decoded = self.decode(ids, skip_special=False)
161
+ if decoded == s:
162
+ ok += 1
163
+ except Exception:
164
+ pass
165
+ return ok / len(test_texts)
166
+
167
+
168
+ __all__ = [
169
+ "BBPETokenizer",
170
+ "BOS_TOKEN", "PAD_TOKEN", "EOS_TOKEN", "UNK_TOKEN",
171
+ "BOS_ID", "PAD_ID", "EOS_ID", "UNK_ID",
172
+ "SPECIAL_TOKENS",
173
+ ]