Sujit-Tokenizer 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .tokenizer import CustomByteLevelBPETokenizer
2
+
3
+ __all__ = ["CustomByteLevelBPETokenizer"]
@@ -0,0 +1,374 @@
1
+ from __future__ import annotations
2
+
3
+ import pickle
4
+ from collections import defaultdict
5
+ from typing import Dict, List, Tuple
6
+
7
+
8
+ class CustomByteLevelBPETokenizer:
9
+ """
10
+ Custom Byte-Level BPE Tokenizer.
11
+
12
+ Features:
13
+ - Byte-level tokenization
14
+ - BPE training
15
+ - Encoding / Decoding
16
+ - Save / Load model
17
+ """
18
+
19
+ def __init__(self, vocab_size: int = 1000):
20
+
21
+ if vocab_size < 260:
22
+ raise ValueError(
23
+ "vocab_size must be >= 260 "
24
+ "(256 bytes + 4 special tokens)"
25
+ )
26
+
27
+ self.vocab_size = vocab_size
28
+
29
+ self.special_tokens = {
30
+ "<PAD>": 0,
31
+ "<UNK>": 1,
32
+ "<BOS>": 2,
33
+ "<EOS>": 3,
34
+ }
35
+
36
+ self.vocab: Dict[bytes, int] = {}
37
+
38
+ # Initialize byte vocabulary
39
+ for i in range(256):
40
+ self.vocab[bytes([i])] = (
41
+ i + len(self.special_tokens)
42
+ )
43
+
44
+ # Add special tokens
45
+ for token, token_id in self.special_tokens.items():
46
+ self.vocab[token.encode()] = token_id
47
+
48
+ self.inverse_vocab: Dict[int, bytes] = {
49
+ idx: token
50
+ for token, idx in self.vocab.items()
51
+ }
52
+
53
+ self.merges: List[
54
+ Tuple[bytes, bytes]
55
+ ] = []
56
+
57
+ def text_to_bytes(
58
+ self,
59
+ text: str
60
+ ) -> List[int]:
61
+
62
+ if not isinstance(text, str):
63
+ raise TypeError(
64
+ "text must be a string"
65
+ )
66
+
67
+ return list(text.encode("utf-8"))
68
+
69
+ def get_pair_frequencies(
70
+ self,
71
+ corpus: List[List[bytes]]
72
+ ) -> Dict[Tuple[bytes, bytes], int]:
73
+
74
+ pair_freq = defaultdict(int)
75
+
76
+ for tokens in corpus:
77
+ for i in range(len(tokens) - 1):
78
+ pair = (
79
+ tokens[i],
80
+ tokens[i + 1]
81
+ )
82
+ pair_freq[pair] += 1
83
+
84
+ return pair_freq
85
+
86
+ def merge_pair(
87
+ self,
88
+ pair: Tuple[bytes, bytes],
89
+ corpus: List[List[bytes]]
90
+ ) -> List[List[bytes]]:
91
+
92
+ updated_corpus = []
93
+
94
+ for tokens in corpus:
95
+
96
+ merged_tokens = []
97
+ i = 0
98
+
99
+ while i < len(tokens):
100
+
101
+ if (
102
+ i < len(tokens) - 1
103
+ and tokens[i] == pair[0]
104
+ and tokens[i + 1] == pair[1]
105
+ ):
106
+ merged_tokens.append(
107
+ tokens[i] + tokens[i + 1]
108
+ )
109
+ i += 2
110
+
111
+ else:
112
+ merged_tokens.append(tokens[i])
113
+ i += 1
114
+
115
+ updated_corpus.append(
116
+ merged_tokens
117
+ )
118
+
119
+ return updated_corpus
120
+
121
+ def train(
122
+ self,
123
+ corpus: List[str]
124
+ ) -> None:
125
+
126
+ if not corpus:
127
+ raise ValueError(
128
+ "Corpus cannot be empty"
129
+ )
130
+
131
+ tokenized_corpus = []
132
+
133
+ for text in corpus:
134
+ tokens = [
135
+ bytes([b])
136
+ for b in self.text_to_bytes(text)
137
+ ]
138
+ tokenized_corpus.append(tokens)
139
+
140
+ num_merges = (
141
+ self.vocab_size -
142
+ len(self.vocab)
143
+ )
144
+
145
+ print("=" * 50)
146
+ print("Training Byte-Level BPE")
147
+ print("=" * 50)
148
+
149
+ for step in range(
150
+ max(0, num_merges)
151
+ ):
152
+
153
+ pair_freq = (
154
+ self.get_pair_frequencies(
155
+ tokenized_corpus
156
+ )
157
+ )
158
+
159
+ if not pair_freq:
160
+ break
161
+
162
+ best_pair = max(
163
+ pair_freq,
164
+ key=pair_freq.get
165
+ )
166
+
167
+ merged_token = (
168
+ best_pair[0]
169
+ + best_pair[1]
170
+ )
171
+
172
+ self.merges.append(
173
+ best_pair
174
+ )
175
+
176
+ if (
177
+ merged_token
178
+ not in self.vocab
179
+ ):
180
+ token_id = len(
181
+ self.vocab
182
+ )
183
+
184
+ self.vocab[
185
+ merged_token
186
+ ] = token_id
187
+
188
+ self.inverse_vocab[
189
+ token_id
190
+ ] = merged_token
191
+
192
+ tokenized_corpus = (
193
+ self.merge_pair(
194
+ best_pair,
195
+ tokenized_corpus
196
+ )
197
+ )
198
+
199
+ if (
200
+ step + 1
201
+ ) % 100 == 0:
202
+ print(
203
+ f"Completed "
204
+ f"{step + 1} merges"
205
+ )
206
+
207
+ print(
208
+ f"Training Complete. "
209
+ f"Vocabulary Size: "
210
+ f"{len(self.vocab)}"
211
+ )
212
+
213
+ def apply_bpe(
214
+ self,
215
+ byte_tokens: List[int]
216
+ ) -> List[bytes]:
217
+
218
+ tokens = [
219
+ bytes([b])
220
+ for b in byte_tokens
221
+ ]
222
+
223
+ for pair in self.merges:
224
+
225
+ merged_tokens = []
226
+ i = 0
227
+
228
+ while i < len(tokens):
229
+
230
+ if (
231
+ i < len(tokens) - 1
232
+ and tokens[i] == pair[0]
233
+ and tokens[i + 1] == pair[1]
234
+ ):
235
+ merged_tokens.append(
236
+ tokens[i]
237
+ + tokens[i + 1]
238
+ )
239
+ i += 2
240
+
241
+ else:
242
+ merged_tokens.append(
243
+ tokens[i]
244
+ )
245
+ i += 1
246
+
247
+ tokens = merged_tokens
248
+
249
+ return tokens
250
+
251
+ def encode(
252
+ self,
253
+ text: str
254
+ ) -> List[int]:
255
+
256
+ byte_tokens = (
257
+ self.text_to_bytes(text)
258
+ )
259
+
260
+ bpe_tokens = self.apply_bpe(
261
+ byte_tokens
262
+ )
263
+
264
+ token_ids = [
265
+ self.special_tokens[
266
+ "<BOS>"
267
+ ]
268
+ ]
269
+
270
+ for token in bpe_tokens:
271
+
272
+ token_ids.append(
273
+ self.vocab.get(
274
+ token,
275
+ self.special_tokens[
276
+ "<UNK>"
277
+ ]
278
+ )
279
+ )
280
+
281
+ token_ids.append(
282
+ self.special_tokens[
283
+ "<EOS>"
284
+ ]
285
+ )
286
+
287
+ return token_ids
288
+
289
+ def decode(
290
+ self,
291
+ token_ids: List[int]
292
+ ) -> str:
293
+
294
+ byte_stream = b""
295
+
296
+ for token_id in token_ids:
297
+
298
+ token = (
299
+ self.inverse_vocab.get(
300
+ token_id
301
+ )
302
+ )
303
+
304
+ if token is None:
305
+ continue
306
+
307
+ if token in {
308
+ b"<PAD>",
309
+ b"<UNK>",
310
+ b"<BOS>",
311
+ b"<EOS>",
312
+ }:
313
+ continue
314
+
315
+ byte_stream += token
316
+
317
+ return byte_stream.decode(
318
+ "utf-8",
319
+ errors="replace"
320
+ )
321
+
322
+ def save_model(
323
+ self,
324
+ path: str = "tokenizer.model"
325
+ ) -> None:
326
+
327
+ model_data = {
328
+ "vocab": self.vocab,
329
+ "inverse_vocab":
330
+ self.inverse_vocab,
331
+ "merges": self.merges,
332
+ "special_tokens":
333
+ self.special_tokens,
334
+ "vocab_size":
335
+ self.vocab_size,
336
+ }
337
+
338
+ with open(path, "wb") as f:
339
+ pickle.dump(
340
+ model_data,
341
+ f
342
+ )
343
+
344
+ print(
345
+ f"Tokenizer saved to "
346
+ f"{path}"
347
+ )
348
+
349
+ def load_model(
350
+ self,
351
+ path: str = "tokenizer.model"
352
+ ) -> None:
353
+
354
+ with open(path, "rb") as f:
355
+ model_data = pickle.load(f)
356
+
357
+ self.vocab = model_data["vocab"]
358
+ self.inverse_vocab = (
359
+ model_data["inverse_vocab"]
360
+ )
361
+ self.merges = model_data["merges"]
362
+ self.special_tokens = (
363
+ model_data[
364
+ "special_tokens"
365
+ ]
366
+ )
367
+ self.vocab_size = (
368
+ model_data["vocab_size"]
369
+ )
370
+
371
+ print(
372
+ f"Tokenizer loaded from "
373
+ f"{path}"
374
+ )
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: Sujit-Tokenizer
3
+ Version: 1.0.0
4
+ Summary: Custom Byte-Level BPE Tokenizer built from scratch in Python
5
+ Home-page: https://github.com/iamsspm07/Sujit-Tokenizer
6
+ Author: Sujit Maity
7
+ Author-email: Sujit Maity <sujitmaity.in@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/iamsspm07/Sujit-Tokenizer
10
+ Project-URL: Repository, https://github.com/iamsspm07/Sujit-Tokenizer
11
+ Project-URL: Issues, https://github.com/iamsspm07/Sujit-Tokenizer/issues
12
+ Keywords: tokenizer,bpe,byte-pair-encoding,nlp,llm,machine-learning,artificial-intelligence
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Text Processing
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ Dynamic: author
26
+ Dynamic: home-page
27
+ Dynamic: requires-python
28
+
29
+ # Sujit-Tokenizer
30
+
31
+ A custom Byte-Level BPE (Byte Pair Encoding) Tokenizer implemented from scratch in Python.
32
+
33
+ ## Features
34
+
35
+ * Byte-level tokenization
36
+ * Custom BPE training
37
+ * Text encoding and decoding
38
+ * Save and load tokenizer models
39
+ * UTF-8 support
40
+ * Lightweight and dependency-free
41
+
42
+ ## Installation
43
+
44
+ ### Clone Repository
45
+
46
+ ```bash
47
+ git clone https://github.com/your-username/Sujit-Tokenizer.git
48
+ cd Sujit-Tokenizer
49
+ ```
50
+
51
+ ### Install Package
52
+
53
+ ```bash
54
+ pip install -e .
55
+ ```
56
+
57
+ ## Project Structure
58
+
59
+ ```text
60
+ Sujit-Tokenizer/
61
+
62
+ ├── Sujit_Tokenizer/
63
+ │ ├── __init__.py
64
+ │ └── tokenizer.py
65
+
66
+ ├── corpus.txt
67
+ ├── train_and_test.py
68
+
69
+ ├── README.md
70
+ ├── setup.py
71
+ ├── pyproject.toml
72
+ └── LICENSE
73
+ ```
74
+
75
+ ## Quick Start
76
+
77
+ ### Import Tokenizer
78
+
79
+ ```python
80
+ from Sujit_Tokenizer import CustomByteLevelBPETokenizer
81
+ ```
82
+
83
+ ### Train Tokenizer
84
+
85
+ ```python
86
+ corpus = [
87
+ "Transformers are amazing.",
88
+ "Machine Learning is powerful.",
89
+ "Python is widely used in AI."
90
+ ]
91
+
92
+ tokenizer = CustomByteLevelBPETokenizer(
93
+ vocab_size=1000
94
+ )
95
+
96
+ tokenizer.train(corpus)
97
+ ```
98
+
99
+ ### Save Model
100
+
101
+ ```python
102
+ tokenizer.save_model(
103
+ "tokenizer.model"
104
+ )
105
+ ```
106
+
107
+ ### Load Model
108
+
109
+ ```python
110
+ tokenizer = CustomByteLevelBPETokenizer()
111
+
112
+ tokenizer.load_model(
113
+ "tokenizer.model"
114
+ )
115
+ ```
116
+
117
+ ### Encode Text
118
+
119
+ ```python
120
+ encoded = tokenizer.encode(
121
+ "Transformers use attention."
122
+ )
123
+
124
+ print(encoded)
125
+ ```
126
+
127
+ Example Output:
128
+
129
+ ```python
130
+ [2, 451, 723, 812, 3]
131
+ ```
132
+
133
+ ### Decode Text
134
+
135
+ ```python
136
+ decoded = tokenizer.decode(
137
+ encoded
138
+ )
139
+
140
+ print(decoded)
141
+ ```
142
+
143
+ Output:
144
+
145
+ ```text
146
+ Transformers use attention.
147
+ ```
148
+
149
+ ## Training Workflow
150
+
151
+ ```text
152
+ Corpus
153
+
154
+ Byte Conversion
155
+
156
+ Pair Frequency Counting
157
+
158
+ BPE Merging
159
+
160
+ Vocabulary Construction
161
+
162
+ Tokenizer Model
163
+ ```
164
+
165
+ ## Special Tokens
166
+
167
+ | Token | ID |
168
+ | ----- | -- |
169
+ | <PAD> | 0 |
170
+ | <UNK> | 1 |
171
+ | <BOS> | 2 |
172
+ | <EOS> | 3 |
173
+
174
+ ## Example
175
+
176
+ ```python
177
+ from Sujit_Tokenizer import CustomByteLevelBPETokenizer
178
+
179
+ tokenizer = CustomByteLevelBPETokenizer(
180
+ vocab_size=1000
181
+ )
182
+
183
+ corpus = [
184
+ "Artificial Intelligence",
185
+ "Machine Learning",
186
+ "Deep Learning"
187
+ ]
188
+
189
+ tokenizer.train(corpus)
190
+
191
+ tokenizer.save_model(
192
+ "tokenizer.model"
193
+ )
194
+
195
+ tokenizer.load_model(
196
+ "tokenizer.model"
197
+ )
198
+
199
+ text = "Machine Learning"
200
+
201
+ encoded = tokenizer.encode(text)
202
+ print(encoded)
203
+
204
+ decoded = tokenizer.decode(encoded)
205
+ print(decoded)
206
+ ```
207
+
208
+ ## Use Cases
209
+
210
+ * NLP experiments
211
+ * Tokenization research
212
+ * Educational projects
213
+ * Understanding BPE internals
214
+ * Building custom language models
215
+ * Learning tokenizer architecture
216
+
217
+ ## Future Enhancements
218
+
219
+ * Faster BPE training
220
+ * WordPiece tokenizer
221
+ * SentencePiece tokenizer
222
+ * Parallel processing
223
+ * Vocabulary statistics
224
+ * Token frequency analysis
225
+ * Hugging Face compatibility
226
+
227
+ ## Author
228
+
229
+ Sujit Maity
230
+
231
+ ## License
232
+
233
+ MIT License
@@ -0,0 +1,6 @@
1
+ Sujit_Tokenizer/__init__.py,sha256=jtOimMzgoBHmBIKSgYkr2clMNcEFKVSOfuC9UbAERxA,95
2
+ Sujit_Tokenizer/tokenizer.py,sha256=MiitxxpwKPCDxCMj8ZbD0lYbUCgs9DkNflXOiTXvP3A,8422
3
+ sujit_tokenizer-1.0.0.dist-info/METADATA,sha256=zI5sQveoSMHBq-laF_Dei05nVStt4qeSHKQl4MA0TSI,4208
4
+ sujit_tokenizer-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ sujit_tokenizer-1.0.0.dist-info/top_level.txt,sha256=hCBMZDYnvCgIsMrvPaAJNEHh17ktggMIHpVe0Z_sgnc,16
6
+ sujit_tokenizer-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ Sujit_Tokenizer