tov-tokenizer 0.1.0__tar.gz
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.
- tov_tokenizer-0.1.0/PKG-INFO +32 -0
- tov_tokenizer-0.1.0/README.md +16 -0
- tov_tokenizer-0.1.0/pyproject.toml +27 -0
- tov_tokenizer-0.1.0/setup.cfg +4 -0
- tov_tokenizer-0.1.0/tov_tokenizer/__init__.py +9 -0
- tov_tokenizer-0.1.0/tov_tokenizer/core.py +310 -0
- tov_tokenizer-0.1.0/tov_tokenizer/graph.py +171 -0
- tov_tokenizer-0.1.0/tov_tokenizer.egg-info/PKG-INFO +32 -0
- tov_tokenizer-0.1.0/tov_tokenizer.egg-info/SOURCES.txt +10 -0
- tov_tokenizer-0.1.0/tov_tokenizer.egg-info/dependency_links.txt +1 -0
- tov_tokenizer-0.1.0/tov_tokenizer.egg-info/requires.txt +1 -0
- tov_tokenizer-0.1.0/tov_tokenizer.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tov-tokenizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Иерархический TOV-токенизатор с γ-кэшированием и λ-утечкой
|
|
5
|
+
Author: Артём Мехед
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/твой-логин/tov-tokenizer
|
|
8
|
+
Project-URL: Repository, https://github.com/твой-логин/tov-tokenizer
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Requires-Python: >=3.7
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: numpy>=1.19
|
|
16
|
+
|
|
17
|
+
# TOV Tokenizer
|
|
18
|
+
|
|
19
|
+
Иерархический токенизатор на основе Теории Относительности Всего (ТОВ).
|
|
20
|
+
|
|
21
|
+
## Особенности
|
|
22
|
+
|
|
23
|
+
- 🌳 Иерархическое дерево (предложения → слова → подслова → символы)
|
|
24
|
+
- ⚡ γ-кэширование частых токенов
|
|
25
|
+
- 🧹 λ-утечка для редких токенов
|
|
26
|
+
- 🧠 Выбор токенов через потоки массы
|
|
27
|
+
- 🔄 Сохранение/загрузка состояния
|
|
28
|
+
|
|
29
|
+
## Установка
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install tov-tokenizer
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# TOV Tokenizer
|
|
2
|
+
|
|
3
|
+
Иерархический токенизатор на основе Теории Относительности Всего (ТОВ).
|
|
4
|
+
|
|
5
|
+
## Особенности
|
|
6
|
+
|
|
7
|
+
- 🌳 Иерархическое дерево (предложения → слова → подслова → символы)
|
|
8
|
+
- ⚡ γ-кэширование частых токенов
|
|
9
|
+
- 🧹 λ-утечка для редких токенов
|
|
10
|
+
- 🧠 Выбор токенов через потоки массы
|
|
11
|
+
- 🔄 Сохранение/загрузка состояния
|
|
12
|
+
|
|
13
|
+
## Установка
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install tov-tokenizer
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tov-tokenizer"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Иерархический TOV-токенизатор с γ-кэшированием и λ-утечкой"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Артём Мехед"}
|
|
12
|
+
]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=1.19",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/твой-логин/tov-tokenizer"
|
|
27
|
+
Repository = "https://github.com/твой-логин/tov-tokenizer"
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TOV Tokenizer: Токенизация через потоки массы.
|
|
3
|
+
Полностью на основе Теории Относительности Всего.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import numpy as np
|
|
9
|
+
import re
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
12
|
+
|
|
13
|
+
from .graph import TokenGraph, TokenNode
|
|
14
|
+
|
|
15
|
+
class TOVTokenizerTOV:
|
|
16
|
+
"""
|
|
17
|
+
Токенизатор, работающий через TOV-граф.
|
|
18
|
+
- Иерархическое дерево токенов
|
|
19
|
+
- Выбор токенов через потоки массы
|
|
20
|
+
- γ-кэширование
|
|
21
|
+
- λ-утечка редких токенов
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, vocab_size=5000, min_frequency=2):
|
|
25
|
+
self.vocab_size = vocab_size
|
|
26
|
+
self.min_frequency = min_frequency
|
|
27
|
+
self.graph = TokenGraph()
|
|
28
|
+
|
|
29
|
+
# 👇 СПЕЦТОКЕНЫ ВСЕГДА В ПЕРВУЮ ОЧЕРЕДЬ
|
|
30
|
+
self.specials = ['<pad>', '<unk>', '<sos>', '<eos>', '<sep>']
|
|
31
|
+
self.token_to_id = {}
|
|
32
|
+
self.id_to_token = {}
|
|
33
|
+
|
|
34
|
+
for i, spec in enumerate(self.specials):
|
|
35
|
+
self.token_to_id[spec] = i
|
|
36
|
+
self.id_to_token[i] = spec
|
|
37
|
+
|
|
38
|
+
self.stats = {
|
|
39
|
+
'tokens_seen': 0,
|
|
40
|
+
'unique_tokens': 0,
|
|
41
|
+
'pruned_tokens': 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def train(self, texts):
|
|
45
|
+
"""
|
|
46
|
+
Обучает токенизатор на текстах.
|
|
47
|
+
Строит иерархическое дерево.
|
|
48
|
+
"""
|
|
49
|
+
print(f"\n🌳 ОБУЧЕНИЕ TOV-ТОКЕНИЗАТОРА")
|
|
50
|
+
print(f" Текстов: {len(texts)}")
|
|
51
|
+
|
|
52
|
+
for i, text in enumerate(texts):
|
|
53
|
+
if i % 100 == 0 and i > 0:
|
|
54
|
+
print(f" Обработано {i}/{len(texts)} текстов")
|
|
55
|
+
self._add_text_to_graph(text)
|
|
56
|
+
|
|
57
|
+
# Применяем λ-утечку
|
|
58
|
+
pruned = self.graph.prune_rare_tokens(
|
|
59
|
+
threshold=0.01,
|
|
60
|
+
time_window=max(100, len(texts)//10)
|
|
61
|
+
)
|
|
62
|
+
self.stats['pruned_tokens'] = pruned
|
|
63
|
+
|
|
64
|
+
# Строим словарь из частотных токенов
|
|
65
|
+
self._build_vocab_from_graph()
|
|
66
|
+
|
|
67
|
+
print(f"✅ TOV-токенизатор готов:")
|
|
68
|
+
print(f" • Узлов в графе: {len(self.graph.nodes)}")
|
|
69
|
+
print(f" • Удалено редких: {pruned}")
|
|
70
|
+
print(f" • Размер словаря: {len(self.token_to_id)}")
|
|
71
|
+
|
|
72
|
+
def _add_text_to_graph(self, text):
|
|
73
|
+
"""Добавляет текст в граф с иерархией"""
|
|
74
|
+
# Разбиваем на предложения
|
|
75
|
+
sentences = re.split(r'[.!?]+', text)
|
|
76
|
+
|
|
77
|
+
for sent in sentences:
|
|
78
|
+
if not sent.strip():
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# Добавляем предложение
|
|
82
|
+
sent_node = self.graph.add_token_node(
|
|
83
|
+
sent.strip(), sent[:30] + "...", "sentence"
|
|
84
|
+
)
|
|
85
|
+
self.graph.add_edge(str(self.graph.root.token_id), str(sent_node.token_id))
|
|
86
|
+
sent_node.update_frequency(self.graph.time_step)
|
|
87
|
+
|
|
88
|
+
# Разбиваем на слова
|
|
89
|
+
words = re.findall(r'\w+', sent.lower())
|
|
90
|
+
|
|
91
|
+
for word in words:
|
|
92
|
+
if len(word) < 2:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# Добавляем слово
|
|
96
|
+
word_node = self.graph.add_token_node(word, word, "word")
|
|
97
|
+
self.graph.add_edge(str(sent_node.token_id), str(word_node.token_id))
|
|
98
|
+
word_node.update_frequency(self.graph.time_step)
|
|
99
|
+
|
|
100
|
+
# Разбиваем на подслова (2-4 символа)
|
|
101
|
+
subwords = self._generate_subwords(word)
|
|
102
|
+
for subword in subwords:
|
|
103
|
+
sub_node = self.graph.add_token_node(subword, subword, "subword")
|
|
104
|
+
self.graph.add_edge(str(word_node.token_id), str(sub_node.token_id))
|
|
105
|
+
sub_node.update_frequency(self.graph.time_step)
|
|
106
|
+
|
|
107
|
+
# Разбиваем на символы
|
|
108
|
+
for i, char in enumerate(word):
|
|
109
|
+
char_node = self.graph.add_token_node(
|
|
110
|
+
char, char, "char"
|
|
111
|
+
)
|
|
112
|
+
self.graph.add_edge(str(word_node.token_id), str(char_node.token_id))
|
|
113
|
+
char_node.update_frequency(self.graph.time_step)
|
|
114
|
+
|
|
115
|
+
self.graph.time_step += 1
|
|
116
|
+
|
|
117
|
+
def _generate_subwords(self, word, min_len=2, max_len=4):
|
|
118
|
+
"""Генерирует подслова для BPE-like токенизации"""
|
|
119
|
+
subwords = []
|
|
120
|
+
word_len = len(word)
|
|
121
|
+
for length in range(min_len, min(max_len, word_len) + 1):
|
|
122
|
+
for i in range(word_len - length + 1):
|
|
123
|
+
subword = word[i:i+length]
|
|
124
|
+
subwords.append(subword)
|
|
125
|
+
return list(set(subwords)) # уникальные
|
|
126
|
+
|
|
127
|
+
def _build_vocab_from_graph(self):
|
|
128
|
+
"""Строит словарь из графа, беря самые частотные узлы"""
|
|
129
|
+
if len(self.graph.nodes) <= 1:
|
|
130
|
+
print("⚠️ Граф пуст, использую только спецтокены")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# Сортируем узлы по частоте
|
|
134
|
+
nodes_by_freq = sorted(
|
|
135
|
+
self.graph.nodes.values(),
|
|
136
|
+
key=lambda n: n.frequency if hasattr(n, 'frequency') else 0,
|
|
137
|
+
reverse=True
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Обычные токены (начиная после спецтокенов)
|
|
141
|
+
idx = len(self.specials)
|
|
142
|
+
for node in nodes_by_freq:
|
|
143
|
+
if not hasattr(node, 'level') or node.level == 'root':
|
|
144
|
+
continue
|
|
145
|
+
if idx >= self.vocab_size:
|
|
146
|
+
break
|
|
147
|
+
if hasattr(node, 'token_str') and node.token_str not in self.token_to_id:
|
|
148
|
+
self.token_to_id[node.token_str] = idx
|
|
149
|
+
self.id_to_token[idx] = node.token_str
|
|
150
|
+
idx += 1
|
|
151
|
+
|
|
152
|
+
def save(self, path):
|
|
153
|
+
"""Сохраняет токенизатор в файл"""
|
|
154
|
+
import pickle
|
|
155
|
+
data = {
|
|
156
|
+
'vocab_size': self.vocab_size,
|
|
157
|
+
'min_frequency': self.min_frequency,
|
|
158
|
+
'token_to_id': self.token_to_id,
|
|
159
|
+
'id_to_token': self.id_to_token,
|
|
160
|
+
'specials': self.specials,
|
|
161
|
+
'stats': self.stats,
|
|
162
|
+
'graph': self.graph # сохраняем весь граф
|
|
163
|
+
}
|
|
164
|
+
with open(path, 'wb') as f:
|
|
165
|
+
pickle.dump(data, f)
|
|
166
|
+
print(f"💾 Токенизатор сохранён в {path}")
|
|
167
|
+
|
|
168
|
+
def load(self, path):
|
|
169
|
+
"""Загружает токенизатор из файла"""
|
|
170
|
+
import pickle
|
|
171
|
+
with open(path, 'rb') as f:
|
|
172
|
+
data = pickle.load(f)
|
|
173
|
+
|
|
174
|
+
self.vocab_size = data['vocab_size']
|
|
175
|
+
self.min_frequency = data['min_frequency']
|
|
176
|
+
self.token_to_id = data['token_to_id']
|
|
177
|
+
self.id_to_token = data['id_to_token']
|
|
178
|
+
self.specials = data['specials']
|
|
179
|
+
self.stats = data['stats']
|
|
180
|
+
self.graph = data['graph']
|
|
181
|
+
|
|
182
|
+
print(f"📂 Токенизатор загружен из {path}")
|
|
183
|
+
print(f" Токенов: {len(self.token_to_id)}")
|
|
184
|
+
print(f" Узлов в графе: {len(self.graph.nodes)}")
|
|
185
|
+
|
|
186
|
+
def encode(self, text, add_special_tokens=True):
|
|
187
|
+
"""
|
|
188
|
+
Токенизирует текст через потоки массы.
|
|
189
|
+
"""
|
|
190
|
+
tokens = []
|
|
191
|
+
|
|
192
|
+
if add_special_tokens:
|
|
193
|
+
tokens.append(self.token_to_id['<sos>']) # теперь точно есть
|
|
194
|
+
|
|
195
|
+
# Разбиваем на предложения
|
|
196
|
+
sentences = re.split(r'[.!?]+', text)
|
|
197
|
+
|
|
198
|
+
for sent in sentences:
|
|
199
|
+
if not sent.strip():
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Ищем узел предложения
|
|
203
|
+
sent_node = self._find_best_node(sent.strip(), 'sentence')
|
|
204
|
+
if sent_node and sent_node.token_str in self.token_to_id:
|
|
205
|
+
tokens.append(self.token_to_id[sent_node.token_str])
|
|
206
|
+
|
|
207
|
+
# Разбиваем на слова
|
|
208
|
+
words = re.findall(r'\w+', sent.lower())
|
|
209
|
+
for word in words:
|
|
210
|
+
# Ищем узел слова
|
|
211
|
+
word_node = self._find_best_node(word, 'word')
|
|
212
|
+
if word_node and word_node.token_str in self.token_to_id:
|
|
213
|
+
tokens.append(self.token_to_id[word_node.token_str])
|
|
214
|
+
else:
|
|
215
|
+
# Если слова нет, разбиваем на символы
|
|
216
|
+
for char in word:
|
|
217
|
+
char_node = self._find_best_node(char, 'char')
|
|
218
|
+
if char_node and char_node.token_str in self.token_to_id:
|
|
219
|
+
tokens.append(self.token_to_id[char_node.token_str])
|
|
220
|
+
else:
|
|
221
|
+
tokens.append(1) # <unk>
|
|
222
|
+
|
|
223
|
+
if add_special_tokens:
|
|
224
|
+
tokens.append(self.token_to_id['<eos>'])
|
|
225
|
+
|
|
226
|
+
return tokens
|
|
227
|
+
|
|
228
|
+
def _find_best_node(self, token_str, level):
|
|
229
|
+
"""Находит лучший узел через поток массы"""
|
|
230
|
+
candidates = []
|
|
231
|
+
for node_id, node in self.graph.nodes.items():
|
|
232
|
+
if (hasattr(node, 'level') and node.level == level and
|
|
233
|
+
hasattr(node, 'token_str') and node.token_str == token_str):
|
|
234
|
+
candidates.append(node)
|
|
235
|
+
|
|
236
|
+
if not candidates:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
# Выбираем по потоку массы от корня
|
|
240
|
+
best_node = None
|
|
241
|
+
best_flow = -float('inf')
|
|
242
|
+
|
|
243
|
+
for node in candidates:
|
|
244
|
+
flow = node.core.a * node.core.output_mass
|
|
245
|
+
if flow > best_flow:
|
|
246
|
+
best_flow = flow
|
|
247
|
+
best_node = node
|
|
248
|
+
|
|
249
|
+
return best_node
|
|
250
|
+
|
|
251
|
+
def decode(self, ids, skip_special=True):
|
|
252
|
+
"""
|
|
253
|
+
Декодирует токены обратно в текст.
|
|
254
|
+
"""
|
|
255
|
+
text = []
|
|
256
|
+
for i in ids:
|
|
257
|
+
token = self.id_to_token.get(i, '<unk>')
|
|
258
|
+
if skip_special and token.startswith('<'):
|
|
259
|
+
continue
|
|
260
|
+
text.append(token)
|
|
261
|
+
return ' '.join(text)
|
|
262
|
+
|
|
263
|
+
def get_stats(self):
|
|
264
|
+
"""Возвращает статистику токенизатора"""
|
|
265
|
+
stats = {
|
|
266
|
+
'vocab_size': len(self.token_to_id),
|
|
267
|
+
'graph_nodes': len(self.graph.nodes),
|
|
268
|
+
'tokens_seen': self.graph.time_step,
|
|
269
|
+
'pruned_tokens': self.stats['pruned_tokens']
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Добавляем статистику графа
|
|
273
|
+
try:
|
|
274
|
+
graph_stats = self.graph.get_stats()
|
|
275
|
+
stats.update(graph_stats)
|
|
276
|
+
except:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
return stats
|
|
280
|
+
|
|
281
|
+
def __len__(self):
|
|
282
|
+
"""Возвращает размер словаря (нужно для len(tokenizer))"""
|
|
283
|
+
return len(self.token_to_id)
|
|
284
|
+
|
|
285
|
+
# ===================== ТЕСТ =====================
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
print("🚀 Тест TOVTokenizerTOV")
|
|
289
|
+
|
|
290
|
+
# Тестовые тексты
|
|
291
|
+
texts = [
|
|
292
|
+
"привет, как дела?",
|
|
293
|
+
"нормально, а у тебя?",
|
|
294
|
+
"отлично! погнали дальше"
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
t = TOVTokenizerTOV(vocab_size=100)
|
|
298
|
+
t.train(texts)
|
|
299
|
+
|
|
300
|
+
for text in texts:
|
|
301
|
+
encoded = t.encode(text)
|
|
302
|
+
decoded = t.decode(encoded)
|
|
303
|
+
print(f"\n📝 Оригинал: {text}")
|
|
304
|
+
print(f" Токенов: {len(encoded)}")
|
|
305
|
+
print(f" Декод: {decoded}")
|
|
306
|
+
|
|
307
|
+
stats = t.get_stats()
|
|
308
|
+
print("\n📊 Статистика:")
|
|
309
|
+
for k, v in stats.items():
|
|
310
|
+
print(f" {k}: {v}")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TOV Token Graph: Иерархическое дерево токенов.
|
|
3
|
+
Предложение -> Слово -> Символ/Подслово.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import numpy as np
|
|
9
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
10
|
+
|
|
11
|
+
# Вместо импорта внешних классов, создадим минимальные версии прямо здесь
|
|
12
|
+
class TOVCore:
|
|
13
|
+
"""Минимальное ядро для токенизатора"""
|
|
14
|
+
def __init__(self, name="layer"):
|
|
15
|
+
self.name = name
|
|
16
|
+
self.a = 0.3
|
|
17
|
+
self.b = 0.4
|
|
18
|
+
self.c = 0.3
|
|
19
|
+
self.output_mass = 0.0
|
|
20
|
+
self.accumulated_mass = 0.0
|
|
21
|
+
self.gamma = 0.0
|
|
22
|
+
self.beta = 0.5
|
|
23
|
+
|
|
24
|
+
def add_learned_mass(self, delta):
|
|
25
|
+
self.accumulated_mass += delta
|
|
26
|
+
self.output_mass = self.accumulated_mass * self.beta
|
|
27
|
+
|
|
28
|
+
class TOVTreeNode:
|
|
29
|
+
"""Минимальный узел для токенизатора"""
|
|
30
|
+
def __init__(self, token_id, embedding):
|
|
31
|
+
self.token_id = token_id
|
|
32
|
+
self.embedding = embedding
|
|
33
|
+
self.children = []
|
|
34
|
+
self.parent = None
|
|
35
|
+
self.core = TOVCore(f"node_{token_id}")
|
|
36
|
+
self.frequency = 0
|
|
37
|
+
self.last_seen = 0
|
|
38
|
+
|
|
39
|
+
def add_child(self, child_node):
|
|
40
|
+
self.children.append(child_node)
|
|
41
|
+
child_node.parent = self
|
|
42
|
+
|
|
43
|
+
class TokenNode(TOVTreeNode):
|
|
44
|
+
"""Узел-токен с дополнительной информацией"""
|
|
45
|
+
def __init__(self, token_id, token_str, level, embedding=None):
|
|
46
|
+
super().__init__(token_id, embedding or np.zeros(20))
|
|
47
|
+
self.token_str = token_str
|
|
48
|
+
self.level = level # 'root', 'sentence', 'word', 'subword', 'char'
|
|
49
|
+
self.frequency = 0
|
|
50
|
+
self.last_seen = 0
|
|
51
|
+
|
|
52
|
+
def update_frequency(self, time_step):
|
|
53
|
+
self.frequency += 1
|
|
54
|
+
self.last_seen = time_step
|
|
55
|
+
# Масса растёт с частотой
|
|
56
|
+
self.core.add_learned_mass(0.1)
|
|
57
|
+
|
|
58
|
+
class TOVGraph:
|
|
59
|
+
"""Минимальный граф для токенизатора"""
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self.nodes = {}
|
|
62
|
+
self.root = None
|
|
63
|
+
|
|
64
|
+
def add_node(self, name, embedding=None):
|
|
65
|
+
"""Добавляет узел в граф"""
|
|
66
|
+
if embedding is None:
|
|
67
|
+
embedding = np.zeros(20)
|
|
68
|
+
node = TOVTreeNode(name, embedding)
|
|
69
|
+
self.nodes[name] = node
|
|
70
|
+
return node
|
|
71
|
+
|
|
72
|
+
def add_edge(self, parent_id, child_id):
|
|
73
|
+
"""Добавляет связь между узлами"""
|
|
74
|
+
if parent_id in self.nodes and child_id in self.nodes:
|
|
75
|
+
self.nodes[parent_id].add_child(self.nodes[child_id])
|
|
76
|
+
|
|
77
|
+
class TokenGraph:
|
|
78
|
+
"""Граф для иерархической токенизации"""
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self.nodes = {}
|
|
81
|
+
self.token_map = {} # token_str -> node_id
|
|
82
|
+
self.time_step = 0
|
|
83
|
+
self.root = self.add_token_node("<root>", "<root>", "root")
|
|
84
|
+
|
|
85
|
+
def add_token_node(self, token_str, display_str, level):
|
|
86
|
+
"""Добавляет узел-токен в граф"""
|
|
87
|
+
token_id = hash(token_str) % 1000000 # простой хеш
|
|
88
|
+
node = TokenNode(token_id, display_str, level)
|
|
89
|
+
self.nodes[str(token_id)] = node
|
|
90
|
+
self.token_map[token_str] = str(token_id)
|
|
91
|
+
return node
|
|
92
|
+
|
|
93
|
+
def add_edge(self, parent_id, child_id):
|
|
94
|
+
"""Добавляет связь между узлами"""
|
|
95
|
+
if parent_id in self.nodes and child_id in self.nodes:
|
|
96
|
+
self.nodes[parent_id].add_child(self.nodes[child_id])
|
|
97
|
+
|
|
98
|
+
def add_sequence(self, tokens, parent_node, level):
|
|
99
|
+
"""Добавляет последовательность токенов как дочерние узлы"""
|
|
100
|
+
nodes = []
|
|
101
|
+
for token in tokens:
|
|
102
|
+
if token not in self.token_map:
|
|
103
|
+
node = self.add_token_node(token, token, level)
|
|
104
|
+
else:
|
|
105
|
+
node_id = self.token_map[token]
|
|
106
|
+
node = self.nodes[node_id]
|
|
107
|
+
|
|
108
|
+
self.add_edge(str(parent_node.token_id), str(node.token_id))
|
|
109
|
+
node.update_frequency(self.time_step)
|
|
110
|
+
nodes.append(node)
|
|
111
|
+
|
|
112
|
+
self.time_step += 1
|
|
113
|
+
return nodes
|
|
114
|
+
|
|
115
|
+
def prune_rare_tokens(self, threshold=0.01, time_window=1000):
|
|
116
|
+
"""Удаляет редкие токены (λ-утечка)"""
|
|
117
|
+
current_time = self.time_step
|
|
118
|
+
to_prune = []
|
|
119
|
+
|
|
120
|
+
for node_id, node in self.nodes.items():
|
|
121
|
+
if not hasattr(node, 'level') or node.level == 'root':
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Если токен не видели давно и частота низкая
|
|
125
|
+
age = current_time - node.last_seen
|
|
126
|
+
if age > time_window and node.frequency < threshold * time_window:
|
|
127
|
+
to_prune.append(node_id)
|
|
128
|
+
|
|
129
|
+
for node_id in to_prune:
|
|
130
|
+
# Удаляем из nodes
|
|
131
|
+
if node_id in self.nodes:
|
|
132
|
+
node = self.nodes[node_id]
|
|
133
|
+
# Удаляем из token_map
|
|
134
|
+
for token_str, tid in list(self.token_map.items()):
|
|
135
|
+
if tid == node_id:
|
|
136
|
+
del self.token_map[token_str]
|
|
137
|
+
# Удаляем сам узел
|
|
138
|
+
del self.nodes[node_id]
|
|
139
|
+
|
|
140
|
+
return len(to_prune)
|
|
141
|
+
|
|
142
|
+
def get_stats(self):
|
|
143
|
+
"""Возвращает статистику графа"""
|
|
144
|
+
stats = {
|
|
145
|
+
'total_nodes': len(self.nodes),
|
|
146
|
+
'unique_tokens': len(self.token_map),
|
|
147
|
+
'time_step': self.time_step
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# Считаем по уровням
|
|
151
|
+
level_counts = {}
|
|
152
|
+
for node in self.nodes.values():
|
|
153
|
+
if hasattr(node, 'level'):
|
|
154
|
+
level_counts[node.level] = level_counts.get(node.level, 0) + 1
|
|
155
|
+
stats['levels'] = level_counts
|
|
156
|
+
|
|
157
|
+
return stats
|
|
158
|
+
|
|
159
|
+
# ===================== ТЕСТ =====================
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
print("🚀 Тест TokenGraph")
|
|
163
|
+
g = TokenGraph()
|
|
164
|
+
print(f"✅ Граф создан")
|
|
165
|
+
print(f" Узлов: {len(g.nodes)}")
|
|
166
|
+
print(f" Корень: {g.root.token_id}")
|
|
167
|
+
|
|
168
|
+
stats = g.get_stats()
|
|
169
|
+
print(f"\n📊 Статистика:")
|
|
170
|
+
for k, v in stats.items():
|
|
171
|
+
print(f" {k}: {v}")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tov-tokenizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Иерархический TOV-токенизатор с γ-кэшированием и λ-утечкой
|
|
5
|
+
Author: Артём Мехед
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/твой-логин/tov-tokenizer
|
|
8
|
+
Project-URL: Repository, https://github.com/твой-логин/tov-tokenizer
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Requires-Python: >=3.7
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: numpy>=1.19
|
|
16
|
+
|
|
17
|
+
# TOV Tokenizer
|
|
18
|
+
|
|
19
|
+
Иерархический токенизатор на основе Теории Относительности Всего (ТОВ).
|
|
20
|
+
|
|
21
|
+
## Особенности
|
|
22
|
+
|
|
23
|
+
- 🌳 Иерархическое дерево (предложения → слова → подслова → символы)
|
|
24
|
+
- ⚡ γ-кэширование частых токенов
|
|
25
|
+
- 🧹 λ-утечка для редких токенов
|
|
26
|
+
- 🧠 Выбор токенов через потоки массы
|
|
27
|
+
- 🔄 Сохранение/загрузка состояния
|
|
28
|
+
|
|
29
|
+
## Установка
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install tov-tokenizer
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
tov_tokenizer/__init__.py
|
|
4
|
+
tov_tokenizer/core.py
|
|
5
|
+
tov_tokenizer/graph.py
|
|
6
|
+
tov_tokenizer.egg-info/PKG-INFO
|
|
7
|
+
tov_tokenizer.egg-info/SOURCES.txt
|
|
8
|
+
tov_tokenizer.egg-info/dependency_links.txt
|
|
9
|
+
tov_tokenizer.egg-info/requires.txt
|
|
10
|
+
tov_tokenizer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.19
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tov_tokenizer
|