microflame 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.
File without changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agoth Arop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: microflame
3
+ Version: 0.1.0
4
+ Summary: A minimal PyTorch training loop with checkpointing, resuming, and plotting built in.
5
+ Project-URL: Homepage, https://github.com/agoth24/microflame
6
+ Project-URL: Repository, https://github.com/agoth24/microflame
7
+ Author: Agoth Arop
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: deep-learning,machine-learning,pytorch,trainer,training
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: matplotlib>=3.0
20
+ Requires-Dist: torch>=2.0
21
+ Requires-Dist: tqdm>=4.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # microflame
25
+
26
+ [![PyPI](https://img.shields.io/pypi/v/microflame.svg)](https://pypi.org/project/microflame/)
27
+ [![Python](https://img.shields.io/pypi/pyversions/microflame.svg)](https://pypi.org/project/microflame/)
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
29
+
30
+ A minimal PyTorch training loop — checkpointing, resuming, and plotting built in.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install microflame
36
+ ```
37
+
38
+ ## Quickstart
39
+
40
+ ```python
41
+ from torch import nn
42
+ from torch.optim import Adam
43
+ from microflame import Trainer
44
+
45
+ model = nn.Sequential(nn.Flatten(), nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 10))
46
+
47
+ trainer = Trainer(
48
+ train_dataloader=train_loader,
49
+ val_dataloader=val_loader,
50
+ model=model,
51
+ loss_fn=nn.CrossEntropyLoss(),
52
+ optimizer=Adam(model.parameters(), lr=1e-3),
53
+ )
54
+
55
+ trainer.fit(num_epochs=10, save_path="checkpoint.pth", save_frequency=5)
56
+ trainer.plot()
57
+ ```
58
+
59
+ Resume from a checkpoint:
60
+
61
+ ```python
62
+ trainer.resume("checkpoint.pth", num_epochs=20)
63
+ ```
64
+
65
+ ## Features
66
+
67
+ - Automatic device selection (CUDA → MPS → CPU)
68
+ - Training + validation loop with live `tqdm` progress and per-epoch metrics
69
+ - Loss/accuracy history tracking
70
+ - Checkpoint save/load (model, optimizer, scheduler, history)
71
+ - Resume training from a checkpoint
72
+ - One-line loss/accuracy plots via matplotlib
73
+
74
+ ## License
75
+
76
+ MIT © Agoth Arop — see [LICENSE](LICENSE).
@@ -0,0 +1,53 @@
1
+ # microflame
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/microflame.svg)](https://pypi.org/project/microflame/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/microflame.svg)](https://pypi.org/project/microflame/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A minimal PyTorch training loop — checkpointing, resuming, and plotting built in.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install microflame
13
+ ```
14
+
15
+ ## Quickstart
16
+
17
+ ```python
18
+ from torch import nn
19
+ from torch.optim import Adam
20
+ from microflame import Trainer
21
+
22
+ model = nn.Sequential(nn.Flatten(), nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 10))
23
+
24
+ trainer = Trainer(
25
+ train_dataloader=train_loader,
26
+ val_dataloader=val_loader,
27
+ model=model,
28
+ loss_fn=nn.CrossEntropyLoss(),
29
+ optimizer=Adam(model.parameters(), lr=1e-3),
30
+ )
31
+
32
+ trainer.fit(num_epochs=10, save_path="checkpoint.pth", save_frequency=5)
33
+ trainer.plot()
34
+ ```
35
+
36
+ Resume from a checkpoint:
37
+
38
+ ```python
39
+ trainer.resume("checkpoint.pth", num_epochs=20)
40
+ ```
41
+
42
+ ## Features
43
+
44
+ - Automatic device selection (CUDA → MPS → CPU)
45
+ - Training + validation loop with live `tqdm` progress and per-epoch metrics
46
+ - Loss/accuracy history tracking
47
+ - Checkpoint save/load (model, optimizer, scheduler, history)
48
+ - Resume training from a checkpoint
49
+ - One-line loss/accuracy plots via matplotlib
50
+
51
+ ## License
52
+
53
+ MIT © Agoth Arop — see [LICENSE](LICENSE).
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "microflame"
7
+ version = "0.1.0"
8
+ description = "A minimal PyTorch training loop with checkpointing, resuming, and plotting built in."
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ authors = [{ name = "Agoth Arop" }]
12
+ keywords = ["pytorch", "training", "deep-learning", "machine-learning", "trainer"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ dependencies = ["torch>=2.0", "tqdm>=4.0", "matplotlib>=3.0"]
23
+ readme = "README.md"
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/agoth24/microflame"
27
+ Repository = "https://github.com/agoth24/microflame"
@@ -0,0 +1,3 @@
1
+ from .trainer import Trainer
2
+
3
+ __all__ = ["Trainer"]
@@ -0,0 +1,209 @@
1
+ import torch
2
+ from torch import nn
3
+ from torch.utils.data import DataLoader
4
+ from torch.optim import Optimizer, lr_scheduler
5
+ import matplotlib.pyplot as plt
6
+ from tqdm import tqdm
7
+
8
+
9
+ class Trainer:
10
+ def __init__(
11
+ self,
12
+ train_dataloader: DataLoader,
13
+ val_dataloader: DataLoader,
14
+ model: nn.Module,
15
+ loss_fn: nn.Module,
16
+ optimizer: Optimizer,
17
+ device: str | None = None,
18
+ scheduler: lr_scheduler.LRScheduler | None = None,
19
+ step_scheduler_per_batch: bool = False
20
+ ):
21
+ self.device = device or (
22
+ "cuda"
23
+ if torch.cuda.is_available()
24
+ else (
25
+ "mps" if (hasattr(torch, "mps") and torch.mps.is_available()) else "cpu"
26
+ )
27
+ )
28
+ self.train_dataloader = train_dataloader
29
+ self.val_dataloader = val_dataloader
30
+ self.model = model.to(self.device)
31
+ self.optimizer = optimizer
32
+ self.scheduler = scheduler
33
+ self.step_scheduler_per_batch = step_scheduler_per_batch
34
+ self.loss_fn = loss_fn
35
+
36
+ self.history = {
37
+ "train_loss": [],
38
+ "train_accuracy": [],
39
+ "val_loss": [],
40
+ "val_accuracy": [],
41
+ }
42
+
43
+ def accuracy(self, prediction, target):
44
+ return (prediction.argmax(1) == target).float().mean().item()
45
+
46
+ def _train_loop(self, current_epoch: int, num_epochs: int):
47
+ dataloader = self.train_dataloader
48
+ train_set_size = len(dataloader.dataset)
49
+ num_batches = len(dataloader)
50
+
51
+ train_loss, correct_preds = 0, 0
52
+ loop = tqdm(
53
+ dataloader, desc=f"Epoch [{current_epoch+1}/{num_epochs}]", leave=False
54
+ )
55
+ self.model.train()
56
+ for X, y in loop:
57
+ X, y = X.to(self.device), y.to(self.device)
58
+
59
+ # forward pass
60
+ pred = self.model(X)
61
+ loss = self.loss_fn(pred, y)
62
+
63
+ # backward pass
64
+ self.optimizer.zero_grad()
65
+ loss.backward()
66
+
67
+ self.optimizer.step()
68
+ if self.scheduler and self.step_scheduler_per_batch:
69
+ self.scheduler.step()
70
+
71
+ train_loss += loss.item()
72
+ correct_preds += self.accuracy(pred, y)
73
+
74
+ loop.set_postfix(Loss=loss.item())
75
+
76
+ train_loss /= num_batches
77
+ train_acc = 100 * correct_preds / train_set_size
78
+ return train_loss, train_acc
79
+
80
+ def _val_loop(self, current_epoch: int, num_epochs: int):
81
+ dataloader = self.val_dataloader
82
+ val_set_size = len(dataloader.dataset)
83
+ num_batches = len(dataloader)
84
+
85
+ val_loss, correct_preds = 0, 0
86
+ self.model.eval()
87
+ with torch.no_grad():
88
+ for X, y in dataloader:
89
+ X, y = X.to(self.device), y.to(self.device)
90
+
91
+ pred = self.model(X)
92
+ loss = self.loss_fn(pred, y)
93
+
94
+ val_loss += loss.item()
95
+ correct_preds += self.accuracy(pred, y)
96
+
97
+ val_loss /= num_batches
98
+ val_acc = 100 * correct_preds / val_set_size
99
+
100
+ return val_loss, val_acc
101
+
102
+ def fit(
103
+ self,
104
+ num_epochs: int,
105
+ save_path: str = "checkpoint.pth",
106
+ save_frequency: int | None = None,
107
+ start_epoch: int = 0,
108
+ ):
109
+
110
+ if start_epoch >= num_epochs:
111
+ raise ValueError("Can't run training loop. Training is already complete")
112
+
113
+ for epoch in range(start_epoch, num_epochs):
114
+ train_loss, train_acc = self._train_loop(
115
+ current_epoch=epoch, num_epochs=num_epochs
116
+ )
117
+ if self.scheduler and not self.step_scheduler_per_batch:
118
+ self.scheduler.step()
119
+ val_loss, val_acc = self._val_loop(
120
+ current_epoch=epoch, num_epochs=num_epochs
121
+ )
122
+
123
+ tqdm.write(
124
+ f"Epoch [{epoch+1}/{num_epochs}] "
125
+ f"loss={train_loss:.3f} acc={train_acc:.3f} | "
126
+ f"val_loss={val_loss:.3f} val_acc={val_acc:.3f}"
127
+ )
128
+
129
+ self.history["train_loss"].append(train_loss)
130
+ self.history["val_loss"].append(val_loss)
131
+ self.history["train_accuracy"].append(train_acc)
132
+ self.history["val_accuracy"].append(val_acc)
133
+
134
+ if save_frequency and (epoch + 1) % save_frequency == 0:
135
+ self.save_checkpoint(save_path, epoch)
136
+
137
+ if save_frequency:
138
+ self.save_checkpoint(save_path, epoch)
139
+
140
+ def save_checkpoint(self, path: str, epoch: int):
141
+ torch.save(
142
+ {
143
+ "epoch": epoch,
144
+ "model_state_dict": self.model.state_dict(),
145
+ "optimizer_state_dict": self.optimizer.state_dict(),
146
+ "scheduler_state_dict": (
147
+ self.scheduler.state_dict() if self.scheduler else None
148
+ ),
149
+ "history": self.history,
150
+ },
151
+ path,
152
+ )
153
+
154
+ def load_checkpoint(self, path: str):
155
+ checkpoint = torch.load(path, map_location=self.device)
156
+ self.model.load_state_dict(checkpoint["model_state_dict"])
157
+ self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
158
+
159
+ # automated warning messages for loading w/ schedulers at checkpoints
160
+ if checkpoint["scheduler_state_dict"] is not None and self.scheduler is None:
161
+ print(
162
+ "Warning: checkpoint has scheduler state but this Trainer has no scheduler — skipping."
163
+ )
164
+ elif self.scheduler is not None and checkpoint["scheduler_state_dict"] is None:
165
+ print(
166
+ "Warning: this Trainer has a scheduler but checkpoint has none — scheduler state not restored."
167
+ )
168
+ elif (
169
+ self.scheduler is not None
170
+ and checkpoint["scheduler_state_dict"] is not None
171
+ ):
172
+ self.scheduler.load_state_dict(checkpoint["scheduler_state_dict"])
173
+
174
+ self.history = checkpoint["history"]
175
+ return checkpoint["epoch"]
176
+
177
+ def resume(
178
+ self,
179
+ path: str,
180
+ num_epochs: int,
181
+ save_frequency: int | None = None,
182
+ save_path: str = "checkpoint.pth",
183
+ ):
184
+ last_epoch = self.load_checkpoint(path)
185
+ self.fit(
186
+ num_epochs=num_epochs,
187
+ start_epoch=last_epoch + 1,
188
+ save_frequency=save_frequency,
189
+ save_path=save_path,
190
+ )
191
+
192
+ def plot(self):
193
+ epochs = range(1, len(self.history["train_loss"]) + 1)
194
+ fig, axes = plt.subplots(1, 2, figsize=(12, 4))
195
+
196
+ axes[0].plot(epochs, self.history["train_loss"], label="Train loss")
197
+ axes[0].plot(epochs, self.history["val_loss"], label="val loss")
198
+ axes[0].set_xlabel("Epoch")
199
+ axes[0].set_ylabel("Loss")
200
+ axes[0].legend()
201
+
202
+ axes[1].plot(epochs, self.history["train_accuracy"], label="Train accuracy")
203
+ axes[1].plot(epochs, self.history["val_accuracy"], label="val accuracy")
204
+ axes[1].set_xlabel("Epoch")
205
+ axes[1].set_ylabel("Accuracy (%)")
206
+ axes[1].legend()
207
+
208
+ plt.tight_layout()
209
+ plt.show()
@@ -0,0 +1,2 @@
1
+ def test_import():
2
+ from microflame import Trainer