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,152 @@
1
+ # Image Classification Tools
2
+
3
+ A lightweight PyTorch toolkit for building and training image classification models.
4
+
5
+ ## Overview
6
+
7
+ This package provides utilities for common image classification tasks:
8
+
9
+ - **Data loading**: Flexible data loaders for torchvision datasets and custom image folders
10
+ - **Model training**: Training loops with progress tracking and validation
11
+ - **Evaluation**: Accuracy metrics, confusion matrices, and performance analysis
12
+ - **Visualization**: Learning curves, probability distributions, and evaluation plots
13
+ - **Hyperparameter optimization**: Optuna integration for automated model tuning
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install image-classification-tools
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ### Basic usage
24
+
25
+ ```python
26
+ import torch
27
+ from pathlib import Path
28
+ from torchvision import datasets, transforms
29
+ from image_classification_tools.pytorch.data import (
30
+ load_datasets, prepare_splits, create_dataloaders
31
+ )
32
+ from image_classification_tools.pytorch.training import train_model
33
+ from image_classification_tools.pytorch.evaluation import evaluate_model
34
+
35
+ # Define transforms
36
+ transform = transforms.Compose([
37
+ transforms.ToTensor(),
38
+ transforms.Normalize((0.5,), (0.5,))
39
+ ])
40
+
41
+ # Load datasets
42
+ train_dataset, test_dataset = load_datasets(
43
+ data_source=datasets.MNIST,
44
+ train_transform=transform,
45
+ eval_transform=transform,
46
+ download=True,
47
+ root=Path('./data/mnist')
48
+ )
49
+
50
+ # Prepare splits
51
+ train_dataset, val_dataset, test_dataset = prepare_splits(
52
+ train_dataset=train_dataset,
53
+ test_dataset=test_dataset,
54
+ train_val_split=0.8
55
+ )
56
+
57
+ # Create dataloaders
58
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
59
+ train_loader, val_loader, test_loader = create_dataloaders(
60
+ train_dataset, val_dataset, test_dataset,
61
+ batch_size=64,
62
+ preload_to_memory=True,
63
+ device=device
64
+ )
65
+
66
+ # Define model, criterion, optimizer
67
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
68
+
69
+ model = torch.nn.Sequential(
70
+ torch.nn.Flatten(),
71
+ torch.nn.Linear(784, 128),
72
+ torch.nn.ReLU(),
73
+ torch.nn.Linear(128, 10)
74
+ ).to(device)
75
+
76
+ criterion = torch.nn.CrossEntropyLoss()
77
+ optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
78
+
79
+ # Train with lazy loading (moves batches to device during training)
80
+ history = train_model(
81
+ model=model,
82
+ train_loader=train_loader,
83
+ val_loader=val_loader,
84
+ criterion=criterion,
85
+ optimizer=optimizer,
86
+ device=device,
87
+ lazy_loading=True, # Set False if data already on device
88
+ epochs=10
89
+ )
90
+
91
+ # Evaluate
92
+ accuracy, predictions, labels = evaluate_model(model, test_loader)
93
+ print(f'Test accuracy: {accuracy:.2f}%')
94
+ ```
95
+
96
+ ### Hyperparameter optimization
97
+
98
+ ```python
99
+ from image_classification_tools.pytorch.hyperparameter_optimization import create_objective
100
+ import optuna
101
+
102
+ # Define search space
103
+ search_space = {
104
+ 'batch_size': [32, 64, 128],
105
+ 'n_conv_blocks': (1, 3),
106
+ 'initial_filters': [16, 32, 64],
107
+ 'n_fc_layers': (1, 3),
108
+ 'conv_dropout_rate': (0.1, 0.5),
109
+ 'fc_dropout_rate': (0.3, 0.7),
110
+ 'learning_rate': (1e-4, 1e-2, 'log'),
111
+ 'optimizer': ['Adam', 'SGD'],
112
+ 'weight_decay': (1e-6, 1e-3, 'log')
113
+ }
114
+
115
+ # Create objective function
116
+ objective = create_objective(
117
+ data_dir='./data',
118
+ train_transform=transform,
119
+ eval_transform=transform,
120
+ n_epochs=20,
121
+ device=device,
122
+ num_classes=10,
123
+ in_channels=1,
124
+ search_space=search_space
125
+ )
126
+
127
+ # Run optimization
128
+ study = optuna.create_study(direction='maximize')
129
+ study.optimize(objective, n_trials=50)
130
+ ```
131
+
132
+ ## Requirements
133
+
134
+ - Python ≥ 3.10
135
+ - PyTorch ≥ 2.0.0
136
+ - torchvision ≥ 0.15.0
137
+ - numpy
138
+ - matplotlib
139
+ - optuna (optional, for hyperparameter optimization)
140
+
141
+ ## Documentation
142
+
143
+ Full documentation is available at: https://gperdrizet.github.io/CIFAR10/
144
+
145
+ ## Demo project
146
+
147
+ See a complete example of using this package for CIFAR-10 classification:
148
+ https://github.com/gperdrizet/CIFAR10
149
+
150
+ ## License
151
+
152
+ GPLv3
@@ -0,0 +1,6 @@
1
+ """Image Classification Tools - General-purpose PyTorch image classification utilities.
2
+
3
+ This package provides tools for building, training, and evaluating image classification
4
+ models with PyTorch. It includes utilities for data loading, model training, evaluation,
5
+ hyperparameter optimization, and visualization.
6
+ """
@@ -0,0 +1,46 @@
1
+ '''PyTorch utilities for image classification.'''
2
+
3
+ from image_classification_tools.pytorch.data import (
4
+ load_dataset,
5
+ prepare_splits,
6
+ create_dataloaders,
7
+ generate_augmented_data
8
+ )
9
+ from image_classification_tools.pytorch.evaluation import evaluate_model
10
+ from image_classification_tools.pytorch.training import train_model
11
+ from image_classification_tools.pytorch.plotting import (
12
+ plot_sample_images,
13
+ plot_learning_curves,
14
+ plot_confusion_matrix,
15
+ plot_class_probability_distributions,
16
+ plot_evaluation_curves,
17
+ plot_optimization_results
18
+ )
19
+ from image_classification_tools.pytorch.hyperparameter_optimization import (
20
+ create_cnn,
21
+ train_trial,
22
+ create_objective
23
+ )
24
+
25
+ __all__ = [
26
+ # Data loading and preprocessing
27
+ 'load_dataset',
28
+ 'prepare_splits',
29
+ 'create_dataloaders',
30
+ 'generate_augmented_data',
31
+ # Model evaluation
32
+ 'evaluate_model',
33
+ # Model training
34
+ 'train_model',
35
+ # Plotting and visualization
36
+ 'plot_sample_images',
37
+ 'plot_learning_curves',
38
+ 'plot_confusion_matrix',
39
+ 'plot_class_probability_distributions',
40
+ 'plot_evaluation_curves',
41
+ 'plot_optimization_results',
42
+ # Hyperparameter optimization
43
+ 'create_cnn',
44
+ 'train_trial',
45
+ 'create_objective'
46
+ ]
@@ -0,0 +1,416 @@
1
+ '''Data loading and preprocessing functions for image classification datasets.
2
+
3
+ This module provides utilities for loading datasets (including CIFAR-10) and creating
4
+ PyTorch DataLoaders with support for custom transforms and device preloading.
5
+ '''
6
+
7
+ # import os
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Tuple
11
+
12
+ import torch
13
+ from torchvision import datasets, transforms
14
+ from torchvision.utils import save_image
15
+ from torch.utils.data import DataLoader, Dataset, TensorDataset, Subset
16
+
17
+
18
+ def load_dataset(
19
+ data_source: Path | type,
20
+ transform: transforms.Compose,
21
+ train: bool = True,
22
+ download: bool = False,
23
+ **dataset_kwargs
24
+ ) -> Dataset:
25
+ '''Load a single dataset from a directory or PyTorch dataset class.
26
+
27
+ This function provides a flexible interface for loading image classification datasets.
28
+ It supports both PyTorch built-in datasets (CIFAR-10, CIFAR-100, MNIST, etc.) and
29
+ custom datasets stored in directories following the ImageFolder structure.
30
+
31
+ Args:
32
+ data_source: Either a Path to a directory containing train/ or test/ subdirectory,
33
+ or a PyTorch dataset class (e.g., datasets.CIFAR10)
34
+ transform: Transforms to apply to the data
35
+ train: If True, load training data. If False, load test data (default: True)
36
+ download: Whether to download the dataset if using a PyTorch dataset class.
37
+ Ignored for directory-based datasets.
38
+ **dataset_kwargs: Additional keyword arguments passed to the dataset class
39
+ (e.g., root='data/pytorch/cifar10')
40
+
41
+ Returns:
42
+ Dataset object
43
+
44
+ Examples:
45
+ # Load CIFAR-10 training data
46
+ train_dataset = load_dataset(
47
+ data_source=datasets.CIFAR10,
48
+ transform=transform,
49
+ train=True,
50
+ root='data/cifar10'
51
+ )
52
+
53
+ # Load from ImageFolder
54
+ train_dataset = load_dataset(
55
+ data_source=Path('data/my_dataset'),
56
+ transform=transform,
57
+ train=True
58
+ )
59
+ '''
60
+
61
+ if isinstance(data_source, Path):
62
+ # Directory-based dataset using ImageFolder
63
+ subdir = 'train' if train else 'test'
64
+ data_dir = data_source / subdir
65
+
66
+ if not data_dir.exists():
67
+ raise ValueError(f'{"Training" if train else "Test"} directory not found: {data_dir}')
68
+
69
+ return datasets.ImageFolder(
70
+ root=data_dir,
71
+ transform=transform
72
+ )
73
+
74
+ else:
75
+ # PyTorch dataset class (CIFAR-10, MNIST, etc.)
76
+ dataset_class = data_source
77
+
78
+ return dataset_class(
79
+ train=train,
80
+ download=download,
81
+ transform=transform,
82
+ **dataset_kwargs
83
+ )
84
+
85
+
86
+ def prepare_splits(
87
+ train_dataset: Dataset,
88
+ test_dataset: Dataset | None = None,
89
+ val_size: int = 10000,
90
+ test_size: int | None = None
91
+ ) -> Tuple[Dataset, Dataset, Dataset]:
92
+ '''Split training dataset into train/val(/test) splits.
93
+
94
+ The splitting behavior depends on whether a separate test dataset is provided:
95
+ - If test_dataset is provided: Split train_dataset into train/val only (2-way split)
96
+ - If test_dataset is None: Split train_dataset into train/val/test (3-way split)
97
+
98
+ Args:
99
+ train_dataset: Training dataset to split
100
+ test_dataset: Test dataset. If None, test set will be split from train_dataset.
101
+ val_size: Number of images to use for validation
102
+ test_size: Number of images to reserve for testing when test_dataset is None.
103
+ Only used when test_dataset is None. If None when test_dataset is None,
104
+ raises ValueError.
105
+
106
+ Returns:
107
+ Tuple of (train_dataset, val_dataset, test_dataset)
108
+
109
+ Examples:
110
+ # 2-way split: Pass separate test set
111
+ train_ds, val_ds, test_ds = prepare_splits(
112
+ train_dataset=my_train_data,
113
+ test_dataset=my_test_data, # Use this for testing
114
+ val_size=10000 # 10,000 images for validation
115
+ )
116
+
117
+ # 3-way split: No separate test set
118
+ train_ds, val_ds, test_ds = prepare_splits(
119
+ train_dataset=my_full_data,
120
+ test_dataset=None, # Will split test from train_dataset
121
+ val_size=10000, # 10,000 for validation
122
+ test_size=5000 # 5,000 for testing
123
+ )
124
+ '''
125
+
126
+ if test_dataset is not None:
127
+
128
+ # 2-way split: train/val only, use provided test set
129
+ total_size = len(train_dataset)
130
+
131
+ if val_size >= total_size:
132
+ raise ValueError(f'val_size ({val_size}) must be less than train_dataset size ({total_size})')
133
+
134
+ indices = torch.randperm(total_size).tolist()
135
+
136
+ val_indices = indices[:val_size]
137
+ train_indices = indices[val_size:]
138
+
139
+ train_dataset_final = Subset(train_dataset, train_indices)
140
+ val_dataset_final = Subset(train_dataset, val_indices)
141
+ test_dataset_final = test_dataset
142
+
143
+ else:
144
+
145
+ # 3-way split: train/val/test all from train_dataset
146
+ if test_size is None:
147
+ raise ValueError('test_size must be provided when test_dataset is None')
148
+
149
+ total_size = len(train_dataset)
150
+
151
+ if val_size + test_size >= total_size:
152
+ raise ValueError(
153
+ f'val_size ({val_size}) + test_size ({test_size}) must be less than '
154
+ f'train_dataset size ({total_size})'
155
+ )
156
+
157
+ indices = torch.randperm(total_size).tolist()
158
+
159
+ val_indices = indices[:val_size]
160
+ test_indices = indices[val_size:val_size + test_size]
161
+ train_indices = indices[val_size + test_size:]
162
+
163
+ train_dataset_final = Subset(train_dataset, train_indices)
164
+ val_dataset_final = Subset(train_dataset, val_indices)
165
+ test_dataset_final = Subset(train_dataset, test_indices)
166
+
167
+ return train_dataset_final, val_dataset_final, test_dataset_final
168
+
169
+
170
+ def create_dataloaders(
171
+ train_dataset: Dataset,
172
+ val_dataset: Dataset,
173
+ test_dataset: Dataset,
174
+ batch_size: int,
175
+ shuffle_train: bool = True,
176
+ num_workers: int = 0,
177
+ preload_to_memory: bool = True,
178
+ device: torch.device | None = None,
179
+ **kwargs
180
+ ) -> Tuple[DataLoader, DataLoader, DataLoader]:
181
+ '''Create DataLoaders from prepared datasets with optional memory preloading.
182
+
183
+ This function provides three memory management strategies:
184
+ 1. Lazy loading (preload_to_memory=False): Data stays on disk, loaded per batch
185
+ 2. CPU preloading (preload_to_memory=True, device=cpu): Entire dataset in RAM
186
+ 3. GPU preloading (preload_to_memory=True, device=cuda): Entire dataset in VRAM
187
+
188
+ Args:
189
+ train_dataset: Prepared training dataset
190
+ val_dataset: Prepared validation dataset
191
+ test_dataset: Prepared test dataset
192
+ batch_size: Batch size for all DataLoaders
193
+ shuffle_train: Whether to shuffle training data (default: True)
194
+ num_workers: Number of subprocesses for data loading (default: 0 for single process).
195
+ Note: num_workers is ignored when preload_to_memory=True.
196
+ preload_to_memory: If True, convert datasets to tensors and load into memory.
197
+ If False, keep as lazy-loading Dataset objects (default: True).
198
+ device: Device to preload tensors onto. Only used if preload_to_memory=True.
199
+ If None with preload_to_memory=True, defaults to CPU.
200
+ Common values: torch.device('cpu'), torch.device('cuda')
201
+ **kwargs: Additional keyword arguments passed to DataLoader
202
+ (e.g., pin_memory=True, persistent_workers=True)
203
+
204
+ Returns:
205
+ Tuple of (train_loader, val_loader, test_loader)
206
+
207
+ Examples:
208
+ # Strategy 1: Lazy loading (large datasets)
209
+ train_loader, val_loader, test_loader = create_dataloaders(
210
+ train_ds, val_ds, test_ds,
211
+ batch_size=128,
212
+ num_workers=4,
213
+ pin_memory=True
214
+ )
215
+
216
+ # Strategy 2: CPU preloading (medium datasets)
217
+ train_loader, val_loader, test_loader = create_dataloaders(
218
+ train_ds, val_ds, test_ds,
219
+ batch_size=128,
220
+ preload_to_memory=True,
221
+ device=torch.device('cpu')
222
+ )
223
+
224
+ # Strategy 3: GPU preloading (small datasets, fastest training)
225
+ train_loader, val_loader, test_loader = create_dataloaders(
226
+ train_ds, val_ds, test_ds,
227
+ batch_size=128,
228
+ preload_to_memory=True,
229
+ device=torch.device('cuda')
230
+ )
231
+ '''
232
+
233
+ if preload_to_memory:
234
+
235
+ # Preload datasets to memory
236
+ if device is None:
237
+ device = torch.device('cpu')
238
+
239
+ # Load train data
240
+ X_train = torch.stack([img for img, _ in train_dataset]).to(device)
241
+ y_train = torch.tensor([label for _, label in train_dataset]).to(device)
242
+ train_dataset_final = TensorDataset(X_train, y_train)
243
+
244
+ # Load val data
245
+ X_val = torch.stack([img for img, _ in val_dataset]).to(device)
246
+ y_val = torch.tensor([label for _, label in val_dataset]).to(device)
247
+ val_dataset_final = TensorDataset(X_val, y_val)
248
+
249
+ # Load test data
250
+ X_test = torch.stack([img for img, _ in test_dataset]).to(device)
251
+ y_test = torch.tensor([label for _, label in test_dataset]).to(device)
252
+ test_dataset_final = TensorDataset(X_test, y_test)
253
+
254
+ # When preloading, num_workers should be 0
255
+ num_workers = 0
256
+
257
+ else:
258
+
259
+ # Use datasets as-is for lazy loading
260
+ train_dataset_final = train_dataset
261
+ val_dataset_final = val_dataset
262
+ test_dataset_final = test_dataset
263
+
264
+ train_loader = DataLoader(
265
+ train_dataset_final,
266
+ batch_size=batch_size,
267
+ shuffle=shuffle_train,
268
+ num_workers=num_workers,
269
+ **kwargs
270
+ )
271
+
272
+ val_loader = DataLoader(
273
+ val_dataset_final,
274
+ batch_size=batch_size,
275
+ shuffle=False,
276
+ num_workers=num_workers,
277
+ **kwargs
278
+ )
279
+
280
+ test_loader = DataLoader(
281
+ test_dataset_final,
282
+ batch_size=batch_size,
283
+ shuffle=False,
284
+ num_workers=num_workers,
285
+ **kwargs
286
+ )
287
+
288
+ return train_loader, val_loader, test_loader
289
+
290
+
291
+ def generate_augmented_data(
292
+ train_dataset: Dataset,
293
+ augmentation_transforms: torch.nn.Sequential,
294
+ augmentations_per_image: int,
295
+ save_dir: str | Path,
296
+ class_names: list[str] | None = None,
297
+ chunk_size: int = 5000,
298
+ force_reaugment: bool = False
299
+ ) -> None:
300
+ '''Generate augmented training data and save as ImageFolder-compatible directory structure.
301
+
302
+ This function applies augmentation transforms to create multiple augmented versions of each
303
+ training image and saves them to disk in ImageFolder format. Images are processed in chunks
304
+ to avoid memory issues with large datasets.
305
+
306
+ Args:
307
+ train_dataset: PyTorch Dataset containing training images
308
+ augmentation_transforms: nn.Sequential containing augmentation transforms to apply
309
+ augmentations_per_image: Number of augmented versions to create per image
310
+ save_dir: Directory path to save augmented images in ImageFolder format
311
+ (will create class_0/, class_1/, etc. subdirectories)
312
+ class_names: Optional list of class names. If None, uses numeric class indices.
313
+ chunk_size: Number of images to process per chunk (default: 5000)
314
+ force_reaugment: If True, regenerate even if saved data exists
315
+
316
+ Returns:
317
+ None (saves images to disk)
318
+
319
+ Example:
320
+ >>> generate_augmented_data(
321
+ ... train_dataset=train_dataset,
322
+ ... augmentation_transforms=augmentation_transforms,
323
+ ... augmentations_per_image=3,
324
+ ... save_dir='data/cifar10_augmented',
325
+ ... class_names=['airplane', 'automobile', ...],
326
+ ... chunk_size=5000
327
+ ... )
328
+ >>> # Then load with existing pipeline:
329
+ >>> aug_dataset, _ = load_datasets(
330
+ ... data_source=Path('data/cifar10_augmented'),
331
+ ... transform=eval_transform
332
+ ... )
333
+ '''
334
+
335
+ save_dir = Path(save_dir)
336
+
337
+ # Check if data already exists
338
+ if save_dir.exists() and any(save_dir.iterdir()) and not force_reaugment:
339
+ print(f'Augmented data already exists at {save_dir}')
340
+ print('Use force_reaugment=True to regenerate')
341
+ return
342
+
343
+ # Create directory structure with 'train' subdirectory for ImageFolder compatibility
344
+ if force_reaugment and save_dir.exists():
345
+ shutil.rmtree(save_dir)
346
+
347
+ save_dir.mkdir(parents=True, exist_ok=True)
348
+ train_dir = save_dir / 'train'
349
+ train_dir.mkdir(exist_ok=True)
350
+
351
+ # Get unique classes from dataset
352
+ print('Scanning dataset for classes...')
353
+ all_labels = set()
354
+ for _, label in train_dataset:
355
+ all_labels.add(label if isinstance(label, int) else label.item())
356
+
357
+ unique_classes = sorted(all_labels)
358
+
359
+ # Create class directories inside train/
360
+ for class_idx in unique_classes:
361
+ if class_names and class_idx < len(class_names):
362
+ class_dir = train_dir / class_names[class_idx]
363
+ else:
364
+ class_dir = train_dir / f'class_{class_idx}'
365
+ class_dir.mkdir(exist_ok=True)
366
+
367
+ print(f'Found {len(unique_classes)} classes')
368
+ print(f'Saving augmented images to {save_dir}')
369
+
370
+ original_size = len(train_dataset)
371
+ num_chunks = (original_size + chunk_size - 1) // chunk_size
372
+
373
+ print(f'Processing {original_size} images in {num_chunks} chunk(s)')
374
+ print(f'Generating {augmentations_per_image} augmentations per image')
375
+
376
+ total_saved = 0
377
+
378
+ # Process dataset in chunks
379
+ for chunk_idx in range(num_chunks):
380
+ start_idx = chunk_idx * chunk_size
381
+ end_idx = min((chunk_idx + 1) * chunk_size, original_size)
382
+
383
+ print(f'\nChunk {chunk_idx + 1}/{num_chunks} (images {start_idx}-{end_idx-1})...')
384
+
385
+ # Process each image in chunk
386
+ for idx in range(start_idx, end_idx):
387
+ img, label = train_dataset[idx]
388
+ label_val = label if isinstance(label, int) else label.item()
389
+
390
+ # Determine class directory (inside train/)
391
+ if class_names and label_val < len(class_names):
392
+ class_dir = train_dir / class_names[label_val]
393
+ else:
394
+ class_dir = train_dir / f'class_{label_val}'
395
+
396
+ # Save original image
397
+ img_name = f'img_{idx:06d}_orig.png'
398
+ save_image(img, class_dir / img_name)
399
+ total_saved += 1
400
+
401
+ # Generate and save augmented versions
402
+ for aug_idx in range(augmentations_per_image):
403
+
404
+ img_aug = augmentation_transforms(img.unsqueeze(0)).squeeze(0)
405
+ img_name = f'img_{idx:06d}_aug{aug_idx:02d}.png'
406
+ save_image(img_aug, class_dir / img_name)
407
+ total_saved += 1
408
+
409
+ print(f' Chunk {chunk_idx + 1} complete')
410
+
411
+ print(f'\nAugmentation complete!')
412
+ print(f' Total images saved: {total_saved}')
413
+ print(f' Original images: {original_size}')
414
+ print(f' Augmented images: {total_saved - original_size}')
415
+ print(f' Augmentation factor: {total_saved / original_size:.1f}x')
416
+ print(f' Location: {save_dir}')
@@ -0,0 +1,38 @@
1
+ '''Evaluation functions for models.'''
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ from torch.utils.data import DataLoader
7
+
8
+
9
+ def evaluate_model(
10
+ model: nn.Module,
11
+ test_loader: DataLoader
12
+ ) -> tuple[float, np.ndarray, np.ndarray]:
13
+ '''Evaluate model on test set.
14
+
15
+ Note: Assumes data is already on the correct device.
16
+ '''
17
+
18
+ model.eval()
19
+ correct = 0
20
+ total = 0
21
+ all_predictions = []
22
+ all_labels = []
23
+
24
+ with torch.no_grad():
25
+
26
+ for images, labels in test_loader:
27
+
28
+ outputs = model(images)
29
+ _, predicted = torch.max(outputs.data, 1)
30
+
31
+ total += labels.size(0)
32
+ correct += (predicted == labels).sum().item()
33
+
34
+ all_predictions.extend(predicted.cpu().numpy())
35
+ all_labels.extend(labels.cpu().numpy())
36
+
37
+ accuracy = 100 * correct / total
38
+ return accuracy, np.array(all_predictions), np.array(all_labels)