shadow-tools 0.7.1__tar.gz → 0.7.2__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.
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/PKG-INFO +4 -1
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/README.md +3 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/pyproject.toml +1 -1
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/__init__.py +1 -1
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/generator.py +18 -6
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/.gitignore +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/LICENSE +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/ai.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/async_tools.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/bpe.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/cli.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/config.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/data.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/files.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/hashing.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/http.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/ids.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/network.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/retry.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/security.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/text.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/training.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/validation.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/src/shadowtools/websocket.py +0 -0
- {shadow_tools-0.7.1 → shadow_tools-0.7.2}/tests/test_core.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: shadow-tools
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A practical all-in-one Python toolkit for HTTP, WebSockets, files, data, validation, async work, and CLIs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WilliamLaurento/shadow-tools
|
|
6
6
|
Project-URL: Issues, https://github.com/WilliamLaurento/shadow-tools/issues
|
|
@@ -73,6 +73,9 @@ print(say(bot, "The future of technology", max_tokens=80))
|
|
|
73
73
|
bot.save("my_text_model")
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
Training prints the tokenizer status plus each epoch and loss. Use
|
|
77
|
+
`learn("corpus.txt", epochs=20, verbose=False)` when you do not want updates.
|
|
78
|
+
|
|
76
79
|
Install it with `pip install "shadow-tools[ml,nlp]"`. The package trains the
|
|
77
80
|
tokenizer and Transformer together, then converts predicted token IDs back to
|
|
78
81
|
normal text for you.
|
|
@@ -45,6 +45,9 @@ print(say(bot, "The future of technology", max_tokens=80))
|
|
|
45
45
|
bot.save("my_text_model")
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Training prints the tokenizer status plus each epoch and loss. Use
|
|
49
|
+
`learn("corpus.txt", epochs=20, verbose=False)` when you do not want updates.
|
|
50
|
+
|
|
48
51
|
Install it with `pip install "shadow-tools[ml,nlp]"`. The package trains the
|
|
49
52
|
tokenizer and Transformer together, then converts predicted token IDs back to
|
|
50
53
|
normal text for you.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "shadow-tools"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "A practical all-in-one Python toolkit for HTTP, WebSockets, files, data, validation, async work, and CLIs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -15,7 +15,7 @@ from .text import chunks, slugify, to_camel, to_snake
|
|
|
15
15
|
from .training import SmartTokenizer, fine_tune, make_loaders, predict_text, train, train_text
|
|
16
16
|
from .validation import is_email, is_url, require_keys
|
|
17
17
|
|
|
18
|
-
__version__ = "0.7.
|
|
18
|
+
__version__ = "0.7.2"
|
|
19
19
|
|
|
20
20
|
__all__ = [
|
|
21
21
|
"append_text", "audit_headers", "BPETokenizer", "chunks", "ensure_dir", "env", "file_hash", "GeneratorConfig", "generate_random_ips", "generate_test_ips", "generate_text", "get", "hash_text", "is_email", "is_subdomain_of", "is_url", "learn", "make_password", "make_subdomain", "random_token", "read_csv",
|
|
@@ -19,6 +19,7 @@ class GeneratorConfig:
|
|
|
19
19
|
batch_size: int = 16
|
|
20
20
|
learning_rate: float = 3e-4
|
|
21
21
|
device: str = "auto"
|
|
22
|
+
verbose: bool = True
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class TextGenerator:
|
|
@@ -78,18 +79,24 @@ class TextGenerator:
|
|
|
78
79
|
|
|
79
80
|
return CausalTransformer().to(self.device)
|
|
80
81
|
|
|
81
|
-
def fit(self, texts: str | Sequence[str], *, epochs: int = 10
|
|
82
|
+
def fit(self, texts: str | Sequence[str], *, epochs: int = 10,
|
|
83
|
+
verbose: bool | None = None) -> list[dict[str, float]]:
|
|
82
84
|
"""Train from a large text string or a list of documents."""
|
|
83
85
|
if epochs < 1:
|
|
84
86
|
raise ValueError("epochs must be at least 1.")
|
|
85
87
|
corpus = texts if isinstance(texts, str) else "\n".join(texts)
|
|
86
88
|
if not isinstance(corpus, str) or len(corpus.strip()) < 100:
|
|
87
89
|
raise ValueError("Give the generator at least 100 characters of training text.")
|
|
90
|
+
show_progress = self.config.verbose if verbose is None else verbose
|
|
91
|
+
if show_progress:
|
|
92
|
+
print("Building tokenizer...")
|
|
88
93
|
self.tokenizer = BPETokenizer(vocab_size=self.config.vocab_size, min_frequency=1).train([corpus])
|
|
89
94
|
token_ids = self.tokenizer.encode(corpus, add_special_tokens=False)["input_ids"]
|
|
90
95
|
if len(token_ids) <= self.config.block_size:
|
|
91
96
|
raise ValueError("Training text needs more tokens than block_size.")
|
|
92
97
|
self.model = self._build_model(self.tokenizer.size)
|
|
98
|
+
if show_progress:
|
|
99
|
+
print(f"Tokenizer ready: {self.tokenizer.size} tokens. Training model for {epochs} epoch(s)...")
|
|
93
100
|
torch = self.torch
|
|
94
101
|
block_size = self.config.block_size
|
|
95
102
|
|
|
@@ -119,7 +126,10 @@ class TextGenerator:
|
|
|
119
126
|
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
|
|
120
127
|
optimizer.step()
|
|
121
128
|
total_loss += float(loss.detach().cpu())
|
|
122
|
-
|
|
129
|
+
metrics = {"epoch": float(epoch), "loss": total_loss / len(loader)}
|
|
130
|
+
history.append(metrics)
|
|
131
|
+
if show_progress:
|
|
132
|
+
print(f"Epoch {epoch}/{epochs} | loss: {metrics['loss']:.4f}")
|
|
123
133
|
return history
|
|
124
134
|
|
|
125
135
|
def generate(self, prompt: str, *, max_tokens: int = 80, temperature: float = 0.8,
|
|
@@ -181,11 +191,12 @@ def train_generator(texts: str | Sequence[str], *, epochs: int = 10,
|
|
|
181
191
|
vocab_size: int = 4_000, block_size: int = 64,
|
|
182
192
|
embedding_size: int = 192, heads: int = 6, layers: int = 4,
|
|
183
193
|
batch_size: int = 16, learning_rate: float = 3e-4,
|
|
184
|
-
device: str = "auto") -> tuple[TextGenerator, list[dict[str, float]]]:
|
|
194
|
+
device: str = "auto", verbose: bool = True) -> tuple[TextGenerator, list[dict[str, float]]]:
|
|
185
195
|
"""Train a text generator in one call, without writing model boilerplate."""
|
|
186
196
|
generator = TextGenerator(GeneratorConfig(vocab_size=vocab_size, block_size=block_size,
|
|
187
197
|
embedding_size=embedding_size, heads=heads, layers=layers,
|
|
188
|
-
batch_size=batch_size, learning_rate=learning_rate, device=device
|
|
198
|
+
batch_size=batch_size, learning_rate=learning_rate, device=device,
|
|
199
|
+
verbose=verbose))
|
|
189
200
|
return generator, generator.fit(texts, epochs=epochs)
|
|
190
201
|
|
|
191
202
|
|
|
@@ -194,7 +205,8 @@ def generate_text(generator: TextGenerator, prompt: str, **options: Any) -> str:
|
|
|
194
205
|
return generator.generate(prompt, **options)
|
|
195
206
|
|
|
196
207
|
|
|
197
|
-
def learn(source: str | Path | Sequence[str], *, epochs: int = 10,
|
|
208
|
+
def learn(source: str | Path | Sequence[str], *, epochs: int = 10,
|
|
209
|
+
verbose: bool = True, **options: Any) -> TextGenerator:
|
|
198
210
|
"""Beginner-friendly training: give it a text file, text, or list of texts."""
|
|
199
211
|
text: str | Sequence[str]
|
|
200
212
|
if isinstance(source, Path):
|
|
@@ -207,7 +219,7 @@ def learn(source: str | Path | Sequence[str], *, epochs: int = 10, **options: An
|
|
|
207
219
|
text = source
|
|
208
220
|
else:
|
|
209
221
|
text = source
|
|
210
|
-
generator, _ = train_generator(text, epochs=epochs, **options)
|
|
222
|
+
generator, _ = train_generator(text, epochs=epochs, verbose=verbose, **options)
|
|
211
223
|
return generator
|
|
212
224
|
|
|
213
225
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|