image-classification-tools 0.5.6__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,256 @@
1
+ '''Training functions for models.'''
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.optim as optim
6
+ from torch.utils.data import DataLoader
7
+
8
+
9
+ def train_one_epoch(model, data_loader, criterion, optimizer, device, lazy_loading=False, cyclic_scheduler=None, history=None):
10
+ '''Run one training epoch, tracking metrics per batch.
11
+
12
+ Args:
13
+ model: PyTorch model to train
14
+ data_loader: Training data loader
15
+ criterion: Loss function
16
+ optimizer: Optimizer
17
+ device: Device for training (e.g., 'cuda' or 'cpu')
18
+ lazy_loading: If True, move batches to device during training. If False,
19
+ assumes data is already on device (default: False)
20
+ cyclic_scheduler: Optional CyclicLR scheduler to step after each batch
21
+ history: Optional history dictionary to record batch-level metrics
22
+
23
+ Returns:
24
+ Tuple of (average_loss, accuracy_percentage) for the epoch
25
+ '''
26
+
27
+ model.train()
28
+ running_loss = 0.0
29
+ correct = 0
30
+ total = 0
31
+
32
+ for _, (images, labels) in enumerate(data_loader):
33
+ if lazy_loading:
34
+ images, labels = images.to(device), labels.to(device)
35
+
36
+ optimizer.zero_grad()
37
+ outputs = model(images)
38
+ loss = criterion(outputs, labels)
39
+ loss.backward()
40
+ optimizer.step()
41
+
42
+ # Step cyclic scheduler after each batch
43
+ if cyclic_scheduler is not None:
44
+ cyclic_scheduler.step()
45
+
46
+ # Track batch-level metrics
47
+ batch_loss = loss.item()
48
+ _, predicted = torch.max(outputs, 1)
49
+ batch_correct = (predicted == labels).sum().item()
50
+ batch_total = labels.size(0)
51
+ batch_acc = 100 * batch_correct / batch_total
52
+
53
+ # Record batch metrics if history is provided
54
+ if history is not None:
55
+ history['batch_train_loss'].append(batch_loss)
56
+ history['batch_train_accuracy'].append(batch_acc)
57
+ history['batch_learning_rates'].append(optimizer.param_groups[0]['lr'])
58
+
59
+ if cyclic_scheduler is not None:
60
+ history['batch_base_lrs'].append(cyclic_scheduler.base_lrs[0])
61
+ history['batch_max_lrs'].append(cyclic_scheduler.max_lrs[0])
62
+
63
+ running_loss += batch_loss
64
+ correct += batch_correct
65
+ total += batch_total
66
+
67
+ return running_loss / len(data_loader), 100 * correct / total
68
+
69
+
70
+ def evaluate(model, data_loader, criterion, device, lazy_loading=False):
71
+ '''Evaluate model on a dataset.
72
+
73
+ Args:
74
+ model: PyTorch model to evaluate
75
+ data_loader: Data loader (validation or test set)
76
+ criterion: Loss function
77
+ device: Device for evaluation (e.g., 'cuda' or 'cpu')
78
+ lazy_loading: If True, move batches to device during evaluation. If False,
79
+ assumes data is already on device (default: False)
80
+
81
+ Returns:
82
+ Tuple of (average_loss, accuracy_percentage) for the dataset
83
+ '''
84
+
85
+ model.eval()
86
+ running_loss = 0.0
87
+ correct = 0
88
+ total = 0
89
+
90
+ with torch.no_grad():
91
+ for images, labels in data_loader:
92
+ if lazy_loading:
93
+ images, labels = images.to(device), labels.to(device)
94
+
95
+ outputs = model(images)
96
+ loss = criterion(outputs, labels)
97
+
98
+ running_loss += loss.item()
99
+ _, predicted = torch.max(outputs, 1)
100
+ total += labels.size(0)
101
+ correct += (predicted == labels).sum().item()
102
+
103
+ return running_loss / len(data_loader), 100 * correct / total
104
+
105
+
106
+ def train_model(
107
+ model: nn.Module,
108
+ train_loader: DataLoader,
109
+ val_loader: DataLoader = None,
110
+ criterion: nn.Module = None,
111
+ optimizer: optim.Optimizer = None,
112
+ device: torch.device = None,
113
+ lazy_loading: bool = False,
114
+ cyclic_scheduler = None,
115
+ epoch_scheduler = None,
116
+ lr_schedule: dict = None,
117
+ epochs: int = 10,
118
+ enable_early_stopping: bool = False,
119
+ early_stopping_patience: int = 10,
120
+ print_every: int = 1
121
+ ) -> dict[str, list[float]]:
122
+ '''Training loop with optional validation and early stopping.
123
+
124
+ Tracks metrics at both batch and epoch levels.
125
+
126
+ Args:
127
+ model: PyTorch model to train
128
+ train_loader: Training data loader
129
+ val_loader: Validation data loader (None for training without validation)
130
+ criterion: Loss function
131
+ optimizer: Optimizer
132
+ device: Device for training (e.g., 'cuda' or 'cpu')
133
+ lazy_loading: If True, move batches to device during training. If False,
134
+ assumes data is already on device (default: False)
135
+ cyclic_scheduler: CyclicLR scheduler (steps per batch)
136
+ epoch_scheduler: Epoch-based scheduler like ReduceLROnPlateau (steps per epoch)
137
+ lr_schedule: Optional dict with scheduled LR bounds reduction:
138
+ {'initial_base_lr', 'initial_max_lr', 'final_base_lr',
139
+ 'final_max_lr', 'schedule_epochs'}
140
+ epochs: Maximum number of epochs
141
+ enable_early_stopping: Whether to enable early stopping (default: False)
142
+ early_stopping_patience: Stop if val_loss doesn't improve for this many epochs
143
+ (only used if enable_early_stopping=True and val_loader is not None)
144
+ print_every: Print progress every N epochs
145
+
146
+ Returns:
147
+ Dictionary containing training history with epoch and batch-level metrics
148
+ '''
149
+
150
+ history = {
151
+ # Epoch-level metrics
152
+ 'train_loss': [], 'val_loss': [],
153
+ 'train_accuracy': [], 'val_accuracy': [],
154
+ 'learning_rates': [],
155
+ 'base_lrs': [],
156
+ 'max_lrs': [],
157
+
158
+ # Batch-level metrics
159
+ 'batch_train_loss': [],
160
+ 'batch_train_accuracy': [],
161
+ 'batch_learning_rates': [],
162
+ 'batch_base_lrs': [],
163
+ 'batch_max_lrs': []
164
+ }
165
+
166
+ best_val_loss = float('inf')
167
+ epochs_without_improvement = 0
168
+ best_model_state = None
169
+
170
+ for epoch in range(epochs):
171
+
172
+ # Train and validate (now passing history to track batch metrics)
173
+ train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device, lazy_loading, cyclic_scheduler, history)
174
+
175
+ # Only evaluate on validation set if provided
176
+ if val_loader is not None:
177
+ val_loss, val_acc = evaluate(model, val_loader, criterion, device, lazy_loading)
178
+ else:
179
+ val_loss, val_acc = None, None
180
+
181
+ # Record epoch-level metrics
182
+ history['train_loss'].append(train_loss)
183
+ history['val_loss'].append(val_loss if val_loss is not None else float('nan'))
184
+ history['train_accuracy'].append(train_acc)
185
+ history['val_accuracy'].append(val_acc if val_acc is not None else float('nan'))
186
+ history['learning_rates'].append(optimizer.param_groups[0]['lr'])
187
+
188
+ # Record base and max LR if using cyclic scheduler
189
+ if cyclic_scheduler is not None:
190
+ history['base_lrs'].append(cyclic_scheduler.base_lrs[0])
191
+ history['max_lrs'].append(cyclic_scheduler.max_lrs[0])
192
+
193
+ # Early stopping (only if validation data is provided and early stopping is enabled)
194
+ if enable_early_stopping and val_loader is not None and val_loss is not None:
195
+ if val_loss < best_val_loss:
196
+ best_val_loss = val_loss
197
+ epochs_without_improvement = 0
198
+ best_model_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}
199
+ else:
200
+ epochs_without_improvement += 1
201
+
202
+ # Update LR bounds based on schedule
203
+ if lr_schedule is not None and cyclic_scheduler is not None and epoch < lr_schedule['schedule_epochs']:
204
+ # Linear interpolation of base and max LR
205
+ progress = (epoch + 1) / lr_schedule['schedule_epochs']
206
+ new_base_lr = lr_schedule['initial_base_lr'] * (1 - progress) + lr_schedule['final_base_lr'] * progress
207
+ new_max_lr = lr_schedule['initial_max_lr'] * (1 - progress) + lr_schedule['final_max_lr'] * progress
208
+
209
+ # Update the cyclic scheduler's base and max LRs
210
+ cyclic_scheduler.base_lrs = [new_base_lr]
211
+ cyclic_scheduler.max_lrs = [new_max_lr]
212
+
213
+ # Step epoch-based scheduler (e.g., ReduceLROnPlateau)
214
+ if epoch_scheduler is not None:
215
+ if val_loader is not None and val_loss is not None:
216
+ # ReduceLROnPlateau needs a metric
217
+ if hasattr(epoch_scheduler, 'step') and 'metrics' in epoch_scheduler.step.__code__.co_varnames:
218
+ epoch_scheduler.step(val_loss)
219
+ else:
220
+ epoch_scheduler.step()
221
+ else:
222
+ epoch_scheduler.step()
223
+
224
+ # Print progress
225
+ if (epoch + 1) % print_every == 0 or epoch == 0:
226
+ # Only show LR info if a scheduler is being used
227
+ if cyclic_scheduler is not None or epoch_scheduler is not None:
228
+ lr = optimizer.param_groups[0]['lr']
229
+ lr_info = f' - lr: {lr:.6f}'
230
+ else:
231
+ lr_info = ''
232
+
233
+ if val_loader is not None:
234
+ print(
235
+ f'Epoch {epoch+1:3d}/{epochs} - '
236
+ f'loss: {train_loss:.4f} - acc: {train_acc:5.2f}% - '
237
+ f'val_loss: {val_loss:.4f} - val_acc: {val_acc:5.2f}%{lr_info}'
238
+ )
239
+ else:
240
+ print(
241
+ f'Epoch {epoch+1:3d}/{epochs} - '
242
+ f'loss: {train_loss:.4f} - acc: {train_acc:5.2f}%{lr_info}'
243
+ )
244
+
245
+ # Check early stopping (only if validation data is provided and early stopping is enabled)
246
+ if enable_early_stopping and val_loader is not None and epochs_without_improvement >= early_stopping_patience:
247
+ print(f'\nEarly stopping at epoch {epoch + 1}')
248
+ print(f'Best val_loss: {best_val_loss:.4f} at epoch {epoch + 1 - epochs_without_improvement}')
249
+ break
250
+
251
+ # Restore best model (only if early stopping was enabled)
252
+ if enable_early_stopping and best_model_state is not None:
253
+ model.load_state_dict(best_model_state)
254
+ print(f'Restored best model weights')
255
+
256
+ return history
File without changes
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: image-classification-tools
3
+ Version: 0.5.6
4
+ Summary: A lightweight PyTorch toolkit for building and training image classification models
5
+ License: GPLv3
6
+ License-File: LICENSE
7
+ Keywords: Python,Machine learning,Deep learning,CNNs,Computer vision,Image classification,PyTorch,Neural networks
8
+ Author: gperdrizet
9
+ Author-email: george@perdrizet.org
10
+ Requires-Python: >=3.10,<3.13
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
+ Classifier: License :: Other/Proprietary License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
24
+ Provides-Extra: tensorflow
25
+ Requires-Dist: numpy (>=1.24)
26
+ Requires-Dist: torch (>=2.0)
27
+ Requires-Dist: torchvision (>=0.15)
28
+ Project-URL: Documentation, https://gperdrizet.github.io/CIFAR10
29
+ Project-URL: Homepage, https://github.com/gperdrizet/CIFAR10
30
+ Project-URL: Issues, https://github.com/gperdrizet/CIFAR10/issues
31
+ Project-URL: PyPI, https://pypi.org/project/image-classification-tools
32
+ Project-URL: Repository, https://github.com/gperdrizet/CIFAR10
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Image Classification Tools
36
+
37
+ A lightweight PyTorch toolkit for building and training image classification models.
38
+
39
+ ## Overview
40
+
41
+ This package provides utilities for common image classification tasks:
42
+
43
+ - **Data loading**: Flexible data loaders for torchvision datasets and custom image folders
44
+ - **Model training**: Training loops with progress tracking and validation
45
+ - **Evaluation**: Accuracy metrics, confusion matrices, and performance analysis
46
+ - **Visualization**: Learning curves, probability distributions, and evaluation plots
47
+ - **Hyperparameter optimization**: Optuna integration for automated model tuning
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install image-classification-tools
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ ### Basic usage
58
+
59
+ ```python
60
+ import torch
61
+ from pathlib import Path
62
+ from torchvision import datasets, transforms
63
+ from image_classification_tools.pytorch.data import (
64
+ load_datasets, prepare_splits, create_dataloaders
65
+ )
66
+ from image_classification_tools.pytorch.training import train_model
67
+ from image_classification_tools.pytorch.evaluation import evaluate_model
68
+
69
+ # Define transforms
70
+ transform = transforms.Compose([
71
+ transforms.ToTensor(),
72
+ transforms.Normalize((0.5,), (0.5,))
73
+ ])
74
+
75
+ # Load datasets
76
+ train_dataset, test_dataset = load_datasets(
77
+ data_source=datasets.MNIST,
78
+ train_transform=transform,
79
+ eval_transform=transform,
80
+ download=True,
81
+ root=Path('./data/mnist')
82
+ )
83
+
84
+ # Prepare splits
85
+ train_dataset, val_dataset, test_dataset = prepare_splits(
86
+ train_dataset=train_dataset,
87
+ test_dataset=test_dataset,
88
+ train_val_split=0.8
89
+ )
90
+
91
+ # Create dataloaders
92
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
93
+ train_loader, val_loader, test_loader = create_dataloaders(
94
+ train_dataset, val_dataset, test_dataset,
95
+ batch_size=64,
96
+ preload_to_memory=True,
97
+ device=device
98
+ )
99
+
100
+ # Define model, criterion, optimizer
101
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
102
+
103
+ model = torch.nn.Sequential(
104
+ torch.nn.Flatten(),
105
+ torch.nn.Linear(784, 128),
106
+ torch.nn.ReLU(),
107
+ torch.nn.Linear(128, 10)
108
+ ).to(device)
109
+
110
+ criterion = torch.nn.CrossEntropyLoss()
111
+ optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
112
+
113
+ # Train with lazy loading (moves batches to device during training)
114
+ history = train_model(
115
+ model=model,
116
+ train_loader=train_loader,
117
+ val_loader=val_loader,
118
+ criterion=criterion,
119
+ optimizer=optimizer,
120
+ device=device,
121
+ lazy_loading=True, # Set False if data already on device
122
+ epochs=10
123
+ )
124
+
125
+ # Evaluate
126
+ accuracy, predictions, labels = evaluate_model(model, test_loader)
127
+ print(f'Test accuracy: {accuracy:.2f}%')
128
+ ```
129
+
130
+ ### Hyperparameter optimization
131
+
132
+ ```python
133
+ from image_classification_tools.pytorch.hyperparameter_optimization import create_objective
134
+ import optuna
135
+
136
+ # Define search space
137
+ search_space = {
138
+ 'batch_size': [32, 64, 128],
139
+ 'n_conv_blocks': (1, 3),
140
+ 'initial_filters': [16, 32, 64],
141
+ 'n_fc_layers': (1, 3),
142
+ 'conv_dropout_rate': (0.1, 0.5),
143
+ 'fc_dropout_rate': (0.3, 0.7),
144
+ 'learning_rate': (1e-4, 1e-2, 'log'),
145
+ 'optimizer': ['Adam', 'SGD'],
146
+ 'weight_decay': (1e-6, 1e-3, 'log')
147
+ }
148
+
149
+ # Create objective function
150
+ objective = create_objective(
151
+ data_dir='./data',
152
+ train_transform=transform,
153
+ eval_transform=transform,
154
+ n_epochs=20,
155
+ device=device,
156
+ num_classes=10,
157
+ in_channels=1,
158
+ search_space=search_space
159
+ )
160
+
161
+ # Run optimization
162
+ study = optuna.create_study(direction='maximize')
163
+ study.optimize(objective, n_trials=50)
164
+ ```
165
+
166
+ ## Requirements
167
+
168
+ - Python ≥ 3.10
169
+ - PyTorch ≥ 2.0.0
170
+ - torchvision ≥ 0.15.0
171
+ - numpy
172
+ - matplotlib
173
+ - optuna (optional, for hyperparameter optimization)
174
+
175
+ ## Documentation
176
+
177
+ Full documentation is available at: https://gperdrizet.github.io/CIFAR10/
178
+
179
+ ## Demo project
180
+
181
+ See a complete example of using this package for CIFAR-10 classification:
182
+ https://github.com/gperdrizet/CIFAR10
183
+
184
+ ## License
185
+
186
+ GPLv3
187
+
@@ -0,0 +1,13 @@
1
+ image_classification_tools/README.md,sha256=coG92m78R6ySYbIe0rL7aQTfwn8EXY1W7eHzwP4aolU,3885
2
+ image_classification_tools/__init__.py,sha256=fjZulfAxTbbqXAvquR-ogCTSbyqaEExceEe_Kzgx7Yc,318
3
+ image_classification_tools/pytorch/__init__.py,sha256=QZo7kAO9iacfI4HqK7ji3zUrEugo8KwMREHU149nlXY,1261
4
+ image_classification_tools/pytorch/data.py,sha256=ZnrauKpSU--9_Hrkqxf9TBJ2zDKUzuW_tyz1dO-4J6Q,15557
5
+ image_classification_tools/pytorch/evaluation.py,sha256=i4tRYOqWATVqQVkWT_fATWRbzo9ziX2DDkXKPaiQlFE,923
6
+ image_classification_tools/pytorch/hyperparameter_optimization.py,sha256=_KB80E7xnHEB4b7KlISG8nLikTNNcwf_ArZM9xDen8E,11272
7
+ image_classification_tools/pytorch/plotting.py,sha256=7B84_JayDuIL3u7hHa1ZPR0VXTe0lBIXxjJ5e4LLXzo,9738
8
+ image_classification_tools/pytorch/training.py,sha256=NjT-g3YyamtgPeS7SxiwCjX2zunTMQI5oxQJAdoRNK0,10370
9
+ image_classification_tools/tensorflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ image_classification_tools-0.5.6.dist-info/METADATA,sha256=0Kq1Az_Tt5xIFoXcGc7WhFeoGXktNB1RkGmKoBkNjJQ,5464
11
+ image_classification_tools-0.5.6.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
12
+ image_classification_tools-0.5.6.dist-info/licenses/LICENSE,sha256=wtHfRwmCF5-_XUmYwrBKwJkGipvHVmh7GXJOKKeOe2U,1073
13
+ image_classification_tools-0.5.6.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Perdrizet
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.