shadow-tools 0.7.0__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.0 → shadow_tools-0.7.2}/PKG-INFO +8 -6
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/README.md +7 -5
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/pyproject.toml +1 -1
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/__init__.py +4 -4
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/generator.py +38 -4
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/.gitignore +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/LICENSE +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/ai.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/async_tools.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/bpe.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/cli.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/config.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/data.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/files.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/hashing.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/http.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/ids.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/network.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/retry.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/security.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/text.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/training.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/validation.py +0 -0
- {shadow_tools-0.7.0 → shadow_tools-0.7.2}/src/shadowtools/websocket.py +0 -0
- {shadow_tools-0.7.0 → 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
|
|
@@ -66,14 +66,16 @@ word as a separate thing. Install it with `pip install "shadow-tools[nlp]"`.
|
|
|
66
66
|
## Train and generate text from one package
|
|
67
67
|
|
|
68
68
|
```python
|
|
69
|
-
from shadowtools import
|
|
69
|
+
from shadowtools import learn, say
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
model.save("my_text_model")
|
|
71
|
+
bot = learn("corpus.txt", epochs=20)
|
|
72
|
+
print(say(bot, "The future of technology", max_tokens=80))
|
|
73
|
+
bot.save("my_text_model")
|
|
75
74
|
```
|
|
76
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
|
+
|
|
77
79
|
Install it with `pip install "shadow-tools[ml,nlp]"`. The package trains the
|
|
78
80
|
tokenizer and Transformer together, then converts predicted token IDs back to
|
|
79
81
|
normal text for you.
|
|
@@ -38,14 +38,16 @@ word as a separate thing. Install it with `pip install "shadow-tools[nlp]"`.
|
|
|
38
38
|
## Train and generate text from one package
|
|
39
39
|
|
|
40
40
|
```python
|
|
41
|
-
from shadowtools import
|
|
41
|
+
from shadowtools import learn, say
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
model.save("my_text_model")
|
|
43
|
+
bot = learn("corpus.txt", epochs=20)
|
|
44
|
+
print(say(bot, "The future of technology", max_tokens=80))
|
|
45
|
+
bot.save("my_text_model")
|
|
47
46
|
```
|
|
48
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
|
+
|
|
49
51
|
Install it with `pip install "shadow-tools[ml,nlp]"`. The package trains the
|
|
50
52
|
tokenizer and Transformer together, then converts predicted token IDs back to
|
|
51
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"
|
|
@@ -5,7 +5,7 @@ from .bpe import BPETokenizer
|
|
|
5
5
|
from .config import env, require_env, to_bool
|
|
6
6
|
from .files import append_text, ensure_dir, read_text, write_text
|
|
7
7
|
from .hashing import file_hash, hash_text
|
|
8
|
-
from .generator import GeneratorConfig, TextGenerator, generate_text, train_generator
|
|
8
|
+
from .generator import GeneratorConfig, TextGenerator, generate_text, learn, say, train_generator
|
|
9
9
|
from .http import get, request
|
|
10
10
|
from .ids import random_token, timestamp_id, uuid4
|
|
11
11
|
from .network import generate_random_ips, generate_test_ips, is_subdomain_of, make_subdomain, resolve_dns, reverse_dns
|
|
@@ -15,10 +15,10 @@ 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
|
-
"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", "make_password", "make_subdomain", "random_token", "read_csv",
|
|
22
|
-
"read_json", "read_text", "request", "require_env", "require_keys", "retry", "reverse_dns", "resolve_dns", "slugify", "timestamp_id", "to_bool", "to_camel", "to_snake", "TextGenerator", "train", "train_generator", "fine_tune", "make_loaders", "SmartTokenizer", "train_text", "predict_text", "uuid4", "write_csv",
|
|
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",
|
|
22
|
+
"read_json", "read_text", "request", "require_env", "require_keys", "retry", "reverse_dns", "resolve_dns", "say", "slugify", "timestamp_id", "to_bool", "to_camel", "to_snake", "TextGenerator", "train", "train_generator", "fine_tune", "make_loaders", "SmartTokenizer", "train_text", "predict_text", "uuid4", "write_csv",
|
|
23
23
|
"write_json", "write_text", "redact_secrets", "verify_file_hash",
|
|
24
24
|
]
|
|
@@ -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,14 +191,38 @@ 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
|
|
|
192
203
|
def generate_text(generator: TextGenerator, prompt: str, **options: Any) -> str:
|
|
193
204
|
"""Short form for ``generator.generate(prompt)``."""
|
|
194
205
|
return generator.generate(prompt, **options)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def learn(source: str | Path | Sequence[str], *, epochs: int = 10,
|
|
209
|
+
verbose: bool = True, **options: Any) -> TextGenerator:
|
|
210
|
+
"""Beginner-friendly training: give it a text file, text, or list of texts."""
|
|
211
|
+
text: str | Sequence[str]
|
|
212
|
+
if isinstance(source, Path):
|
|
213
|
+
text = source.read_text(encoding="utf-8")
|
|
214
|
+
elif isinstance(source, str):
|
|
215
|
+
try:
|
|
216
|
+
candidate = Path(source)
|
|
217
|
+
text = candidate.read_text(encoding="utf-8") if candidate.is_file() else source
|
|
218
|
+
except OSError:
|
|
219
|
+
text = source
|
|
220
|
+
else:
|
|
221
|
+
text = source
|
|
222
|
+
generator, _ = train_generator(text, epochs=epochs, verbose=verbose, **options)
|
|
223
|
+
return generator
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def say(generator: TextGenerator, prompt: str, **options: Any) -> str:
|
|
227
|
+
"""Beginner-friendly name for generating text from a trained model."""
|
|
228
|
+
return generator.generate(prompt, **options)
|
|
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
|