Samuel-Collins-CV-Benchmarking 1.0.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.
- samuel_collins_cv_benchmarking-1.0.0/LICENSE +21 -0
- samuel_collins_cv_benchmarking-1.0.0/MANIFEST.in +13 -0
- samuel_collins_cv_benchmarking-1.0.0/PKG-INFO +267 -0
- samuel_collins_cv_benchmarking-1.0.0/README.md +216 -0
- samuel_collins_cv_benchmarking-1.0.0/pyproject.toml +53 -0
- samuel_collins_cv_benchmarking-1.0.0/setup.cfg +4 -0
- samuel_collins_cv_benchmarking-1.0.0/src/Samuel_Collins_CV_Benchmarking.egg-info/PKG-INFO +267 -0
- samuel_collins_cv_benchmarking-1.0.0/src/Samuel_Collins_CV_Benchmarking.egg-info/SOURCES.txt +57 -0
- samuel_collins_cv_benchmarking-1.0.0/src/Samuel_Collins_CV_Benchmarking.egg-info/dependency_links.txt +1 -0
- samuel_collins_cv_benchmarking-1.0.0/src/Samuel_Collins_CV_Benchmarking.egg-info/requires.txt +14 -0
- samuel_collins_cv_benchmarking-1.0.0/src/Samuel_Collins_CV_Benchmarking.egg-info/top_level.txt +1 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/__init__.py +10 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/_config.py +19 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/benchmark.py +400 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/classical_models.py +125 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/data_loader.py +847 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/evaluation.py +276 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/neural_models.py +324 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/preprocessing.py +259 -0
- samuel_collins_cv_benchmarking-1.0.0/src/samuel_collins_cv_benchmarking/visualization.py +388 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/conftest.py +28 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/cat/cat_01.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/cat/cat_02.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/cat/cat_03.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/cat/cat_corrupt.jpeg +1 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/dog/dog_01.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/dog/dog_02.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/dog/dog_03.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/horse/horse_01.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/horse/horse_02.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken/horse/horse_03.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken_labels.csv +12 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken_labels.json +46 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/broken_labels.jsonl +11 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/cat/cat_bmp.bmp +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/cat/cat_jpeg.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/cat/cat_jpg.jpg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/cat/cat_png.png +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/cat/cat_tiff.tiff +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/dog/dog_bmp.bmp +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/dog/dog_jpeg.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/dog/dog_jpg.jpg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/dog/dog_png.png +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/dog/dog_tiff.tiff +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/horse/horse_bmp.bmp +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/horse/horse_jpeg.jpeg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/horse/horse_jpg.jpg +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/horse/horse_png.png +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini/horse/horse_tiff.tiff +0 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini_labels.csv +16 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini_labels.json +62 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/fixtures/mini_labels.jsonl +15 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_benchmark.py +333 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_data_loaders.py +1046 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_evaluation.py +518 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_models.py +525 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_preprocessing.py +269 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_stratification.py +213 -0
- samuel_collins_cv_benchmarking-1.0.0/tests/test_visualization.py +341 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Collins
|
|
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,13 @@
|
|
|
1
|
+
# The sdist picked up tests/test_*.py automatically but not conftest.py or
|
|
2
|
+
# the fixture images, which left a test suite that could not run: conftest
|
|
3
|
+
# is what puts src/ on the path and supplies the fixtures_dir fixture.
|
|
4
|
+
include README.md LICENSE
|
|
5
|
+
recursive-include tests *.py
|
|
6
|
+
recursive-include tests/fixtures *.jpeg *.jpg *.png *.bmp *.tiff *.csv *.json *.jsonl
|
|
7
|
+
|
|
8
|
+
# Never ship the datasets, the results, the report or personal notes.
|
|
9
|
+
prune data
|
|
10
|
+
prune benchmark_results
|
|
11
|
+
prune class_notes
|
|
12
|
+
prune report
|
|
13
|
+
prune scripts
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Samuel_Collins_CV_Benchmarking
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Benchmark classical machine-learning and neural-network image classifiers through one public function.
|
|
5
|
+
Author: Samuel Collins
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Samuel Collins
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/srcollins785/Samuel_Collins_CV_Benchmarking
|
|
29
|
+
Project-URL: Repository, https://github.com/srcollins785/Samuel_Collins_CV_Benchmarking
|
|
30
|
+
Keywords: computer-vision,image-classification,benchmarking,scikit-learn
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
35
|
+
Requires-Python: >=3.9
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: numpy>=1.24
|
|
39
|
+
Requires-Dist: pandas>=2.0
|
|
40
|
+
Requires-Dist: pillow>=10.0
|
|
41
|
+
Requires-Dist: scikit-learn>=1.3
|
|
42
|
+
Requires-Dist: matplotlib>=3.7
|
|
43
|
+
Requires-Dist: torch>=2.0
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
46
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
47
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
48
|
+
Provides-Extra: report
|
|
49
|
+
Requires-Dist: markdown>=3.5; extra == "report"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# Samuel_Collins_CV_Benchmarking
|
|
53
|
+
|
|
54
|
+
Benchmark classical machine-learning and neural-network image classifiers
|
|
55
|
+
through a single public function. Give it a labeled image dataset in any of
|
|
56
|
+
four organizations; it standardizes the images, builds one stratified split,
|
|
57
|
+
trains every model on that split, and saves comparable metrics, plots and
|
|
58
|
+
reports.
|
|
59
|
+
|
|
60
|
+
> **Status: scaffolding.** The package layout, metadata and test fixtures are
|
|
61
|
+
> in place. The loaders, models and evaluation pipeline are not implemented
|
|
62
|
+
> yet — `benchmark_image_classification()` validates its arguments and then
|
|
63
|
+
> raises `NotImplementedError`.
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install Samuel_Collins_CV_Benchmarking
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
From a clone, for development:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e ".[dev]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from samuel_collins_cv_benchmarking import benchmark_image_classification
|
|
81
|
+
|
|
82
|
+
results = benchmark_image_classification(
|
|
83
|
+
dataset="./data/animals10_n500/images",
|
|
84
|
+
dataset_type="folder",
|
|
85
|
+
target_labels=["cat", "dog", "horse"],
|
|
86
|
+
color_mode="rgb",
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Parameters
|
|
91
|
+
|
|
92
|
+
| Parameter | Meaning |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `dataset` | Dataset root directory, CSV/JSON/JSONL manifest path, Pandas DataFrame, or NumPy image array |
|
|
95
|
+
| `dataset_type` | One of `"folder"`, `"csv"`, `"json"`, `"array"` |
|
|
96
|
+
| `target_labels` | Class-folder names, manifest label-field name, DataFrame label column, or a label vector |
|
|
97
|
+
| `color_mode` | `"grayscale"` for one channel, `"rgb"` for three |
|
|
98
|
+
|
|
99
|
+
Image size (64x64), random seed (42), split ratio (80/20) and output location
|
|
100
|
+
are internal constants — callers configure nothing beyond the four parameters
|
|
101
|
+
above.
|
|
102
|
+
|
|
103
|
+
## The four dataset organizations
|
|
104
|
+
|
|
105
|
+
**1. Class folders** — each subfolder name is the label. PNG, JPG, JPEG, BMP
|
|
106
|
+
and TIFF are supported.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
data/animals10_n500/images/
|
|
110
|
+
├── cat/cat_001.jpeg
|
|
111
|
+
├── dog/dog_001.jpeg
|
|
112
|
+
└── horse/horse_001.jpeg
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
benchmark_image_classification(
|
|
117
|
+
dataset="./data/animals10_n500/images", dataset_type="folder",
|
|
118
|
+
target_labels=["cat", "dog", "horse"], color_mode="rgb")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**2. CSV manifest** — an `image_path` column plus a label column named by
|
|
122
|
+
`target_labels`. Relative paths resolve from the manifest's own location.
|
|
123
|
+
|
|
124
|
+
```csv
|
|
125
|
+
image_path,class_name
|
|
126
|
+
images/cat/cat_001.jpeg,cat
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
benchmark_image_classification(
|
|
131
|
+
dataset="./data/animals10_n500/labels.csv", dataset_type="csv",
|
|
132
|
+
target_labels="class_name", color_mode="rgb")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**3. JSON / JSONL manifest** — a JSON list of records, or one JSON record per
|
|
136
|
+
line. Every record carries `image_path` and the label field.
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
[{"image_path": "images/cat/cat_001.jpeg", "class_name": "cat"}]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
benchmark_image_classification(
|
|
144
|
+
dataset="./data/animals10_n500/labels.json", dataset_type="json",
|
|
145
|
+
target_labels="class_name", color_mode="rgb")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**4. NumPy array / in-memory** — `dataset` is the image tensor, `target_labels`
|
|
149
|
+
the label vector. Accepted shapes: `(N, H, W)`, `(N, H, W, 1)`, `(N, H, W, 3)`.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
benchmark_image_classification(
|
|
153
|
+
dataset=X_images, dataset_type="array",
|
|
154
|
+
target_labels=y_labels, color_mode="grayscale")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Datasets
|
|
158
|
+
|
|
159
|
+
**RGB - Animals-10.** 10 classes. The images are **not committed to this
|
|
160
|
+
repository**: Animals-10 is assembled from web-scraped photographs, so
|
|
161
|
+
redistributing it here is not appropriate. Rebuild it in one command (needs
|
|
162
|
+
Kaggle API credentials at `~/.kaggle/kaggle.json`):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
python scripts/download_animals10.py # 500/class -> data/animals10_n500
|
|
166
|
+
python scripts/download_animals10.py --per-class 10 # fast smoke set
|
|
167
|
+
python scripts/download_animals10.py --per-class 100 --out data/custom
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Each tier lands in its own directory containing `images/` plus `labels.csv`,
|
|
171
|
+
`labels.json` and `labels.jsonl`, so several sizes coexist:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
data/animals10_n500/
|
|
175
|
+
├── images/<class>/<class>_001.jpeg
|
|
176
|
+
├── labels.csv
|
|
177
|
+
├── labels.json
|
|
178
|
+
└── labels.jsonl
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Sampling method
|
|
182
|
+
|
|
183
|
+
Reported subset: **500 images per class, 5,000 total**, drawn from
|
|
184
|
+
`alessiocorrado99/animals10`. Per class, the file list is sorted, shuffled with
|
|
185
|
+
`random.Random(<english class name>)`, and the first N images that decode
|
|
186
|
+
successfully are taken. Every file is opened, verified and RGB-converted before
|
|
187
|
+
selection; undecodable files are skipped and counted.
|
|
188
|
+
|
|
189
|
+
The seed depends only on the class name, never on N, so the tiers are **nested**:
|
|
190
|
+
`n10` is byte-identical to the first 10 images of `n100`, filenames included.
|
|
191
|
+
A comparison across tiers is therefore a genuine learning curve rather than
|
|
192
|
+
three unrelated samples.
|
|
193
|
+
|
|
194
|
+
Source folder names are Italian and are mapped to English (`cane`->`dog`,
|
|
195
|
+
`gatto`->`cat`, `ragno`->`spider`, ...). The per-class ceiling is set by the
|
|
196
|
+
smallest class, elephant, at 1,446 images.
|
|
197
|
+
|
|
198
|
+
**Grayscale - not yet added.**
|
|
199
|
+
|
|
200
|
+
## Results
|
|
201
|
+
|
|
202
|
+
Two runs over nested subsets of Animals-10, ten classes, RGB at 64x64. The
|
|
203
|
+
tiers share one fixed ordering, so `n100` is a byte-identical subset of
|
|
204
|
+
`n500` and reading across them is a learning curve rather than a comparison
|
|
205
|
+
of unrelated samples. Chance for ten classes is 0.100.
|
|
206
|
+
|
|
207
|
+
| Model | Macro F1 @ 100/class | Macro F1 @ 500/class | change |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| Simple CNN | 0.295 | **0.434** | +47% |
|
|
210
|
+
| SVM | 0.242 | 0.352 | +45% |
|
|
211
|
+
| Random Forest | 0.294 | 0.319 | +9% |
|
|
212
|
+
| Neural Network | 0.178 | 0.281 | +58% |
|
|
213
|
+
| Logistic Regression | 0.213 | 0.225 | +6% |
|
|
214
|
+
| Decision Tree | 0.093 | 0.180 | +94% |
|
|
215
|
+
|
|
216
|
+
At 100 images per class the CNN and Random Forest are tied. At 500 the CNN
|
|
217
|
+
leads by 36%, and Logistic Regression and Random Forest have nearly
|
|
218
|
+
flattened while the CNN and SVM are still climbing steeply. Reporting only
|
|
219
|
+
the smaller run would have supported the conclusion that a CNN and an
|
|
220
|
+
ensemble of trees are equivalent here - true at that size, and misleading as
|
|
221
|
+
a finding.
|
|
222
|
+
|
|
223
|
+
Cost at 500 per class tells a different story from accuracy alone:
|
|
224
|
+
|
|
225
|
+
| Model | Training | Inference |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| SVM | 547.4 s | 150.1 ms/image |
|
|
228
|
+
| Simple CNN | 44.8 s | 0.43 ms/image |
|
|
229
|
+
| Decision Tree | 20.2 s | 0.002 ms/image |
|
|
230
|
+
| Logistic Regression | 9.5 s | 0.040 ms/image |
|
|
231
|
+
| Random Forest | 3.3 s | 0.028 ms/image |
|
|
232
|
+
| Neural Network | 1.3 s | 0.025 ms/image |
|
|
233
|
+
|
|
234
|
+
The SVM buys third place at 75,000 times the Decision Tree's inference cost:
|
|
235
|
+
classifying a thousand images would take it two and a half minutes against
|
|
236
|
+
Random Forest's 0.03 seconds for a slightly better score.
|
|
237
|
+
|
|
238
|
+
Full outputs are under `benchmark_results/<tier>/`. Reproduce them with:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
python scripts/download_animals10.py --per-class 500
|
|
242
|
+
python scripts/run_benchmarks.py
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The 500/class run takes about 13 minutes, 9 of which are the SVM.
|
|
246
|
+
|
|
247
|
+
## Tests
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pytest tests/
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`tests/fixtures/` holds small committed datasets so the suite runs in a clean
|
|
254
|
+
checkout with no downloads:
|
|
255
|
+
|
|
256
|
+
| Fixture | Contents |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `mini/` | 3 classes x 5 images, pristine. One image per required extension (`.jpeg`, `.jpg`, `.png`, `.bmp`, `.tiff`) at five different dimensions, so resizing and aspect handling are exercised. |
|
|
259
|
+
| `broken/` | 3 classes x 3 images plus one undecodable file and, in the manifests, one row pointing at a file that is not on disk. Covers the skip-and-report path. |
|
|
260
|
+
|
|
261
|
+
Each tree has matching `*_labels.csv`, `.json` and `.jsonl` manifests, so all
|
|
262
|
+
four dataset organizations can be tested against committed data.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT. See [LICENSE](LICENSE). The license covers this source code, not the
|
|
267
|
+
third-party image datasets it consumes.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Samuel_Collins_CV_Benchmarking
|
|
2
|
+
|
|
3
|
+
Benchmark classical machine-learning and neural-network image classifiers
|
|
4
|
+
through a single public function. Give it a labeled image dataset in any of
|
|
5
|
+
four organizations; it standardizes the images, builds one stratified split,
|
|
6
|
+
trains every model on that split, and saves comparable metrics, plots and
|
|
7
|
+
reports.
|
|
8
|
+
|
|
9
|
+
> **Status: scaffolding.** The package layout, metadata and test fixtures are
|
|
10
|
+
> in place. The loaders, models and evaluation pipeline are not implemented
|
|
11
|
+
> yet — `benchmark_image_classification()` validates its arguments and then
|
|
12
|
+
> raises `NotImplementedError`.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install Samuel_Collins_CV_Benchmarking
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
From a clone, for development:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from samuel_collins_cv_benchmarking import benchmark_image_classification
|
|
30
|
+
|
|
31
|
+
results = benchmark_image_classification(
|
|
32
|
+
dataset="./data/animals10_n500/images",
|
|
33
|
+
dataset_type="folder",
|
|
34
|
+
target_labels=["cat", "dog", "horse"],
|
|
35
|
+
color_mode="rgb",
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Parameters
|
|
40
|
+
|
|
41
|
+
| Parameter | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `dataset` | Dataset root directory, CSV/JSON/JSONL manifest path, Pandas DataFrame, or NumPy image array |
|
|
44
|
+
| `dataset_type` | One of `"folder"`, `"csv"`, `"json"`, `"array"` |
|
|
45
|
+
| `target_labels` | Class-folder names, manifest label-field name, DataFrame label column, or a label vector |
|
|
46
|
+
| `color_mode` | `"grayscale"` for one channel, `"rgb"` for three |
|
|
47
|
+
|
|
48
|
+
Image size (64x64), random seed (42), split ratio (80/20) and output location
|
|
49
|
+
are internal constants — callers configure nothing beyond the four parameters
|
|
50
|
+
above.
|
|
51
|
+
|
|
52
|
+
## The four dataset organizations
|
|
53
|
+
|
|
54
|
+
**1. Class folders** — each subfolder name is the label. PNG, JPG, JPEG, BMP
|
|
55
|
+
and TIFF are supported.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
data/animals10_n500/images/
|
|
59
|
+
├── cat/cat_001.jpeg
|
|
60
|
+
├── dog/dog_001.jpeg
|
|
61
|
+
└── horse/horse_001.jpeg
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
benchmark_image_classification(
|
|
66
|
+
dataset="./data/animals10_n500/images", dataset_type="folder",
|
|
67
|
+
target_labels=["cat", "dog", "horse"], color_mode="rgb")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**2. CSV manifest** — an `image_path` column plus a label column named by
|
|
71
|
+
`target_labels`. Relative paths resolve from the manifest's own location.
|
|
72
|
+
|
|
73
|
+
```csv
|
|
74
|
+
image_path,class_name
|
|
75
|
+
images/cat/cat_001.jpeg,cat
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
benchmark_image_classification(
|
|
80
|
+
dataset="./data/animals10_n500/labels.csv", dataset_type="csv",
|
|
81
|
+
target_labels="class_name", color_mode="rgb")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**3. JSON / JSONL manifest** — a JSON list of records, or one JSON record per
|
|
85
|
+
line. Every record carries `image_path` and the label field.
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
[{"image_path": "images/cat/cat_001.jpeg", "class_name": "cat"}]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
benchmark_image_classification(
|
|
93
|
+
dataset="./data/animals10_n500/labels.json", dataset_type="json",
|
|
94
|
+
target_labels="class_name", color_mode="rgb")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**4. NumPy array / in-memory** — `dataset` is the image tensor, `target_labels`
|
|
98
|
+
the label vector. Accepted shapes: `(N, H, W)`, `(N, H, W, 1)`, `(N, H, W, 3)`.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
benchmark_image_classification(
|
|
102
|
+
dataset=X_images, dataset_type="array",
|
|
103
|
+
target_labels=y_labels, color_mode="grayscale")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Datasets
|
|
107
|
+
|
|
108
|
+
**RGB - Animals-10.** 10 classes. The images are **not committed to this
|
|
109
|
+
repository**: Animals-10 is assembled from web-scraped photographs, so
|
|
110
|
+
redistributing it here is not appropriate. Rebuild it in one command (needs
|
|
111
|
+
Kaggle API credentials at `~/.kaggle/kaggle.json`):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python scripts/download_animals10.py # 500/class -> data/animals10_n500
|
|
115
|
+
python scripts/download_animals10.py --per-class 10 # fast smoke set
|
|
116
|
+
python scripts/download_animals10.py --per-class 100 --out data/custom
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each tier lands in its own directory containing `images/` plus `labels.csv`,
|
|
120
|
+
`labels.json` and `labels.jsonl`, so several sizes coexist:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
data/animals10_n500/
|
|
124
|
+
├── images/<class>/<class>_001.jpeg
|
|
125
|
+
├── labels.csv
|
|
126
|
+
├── labels.json
|
|
127
|
+
└── labels.jsonl
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sampling method
|
|
131
|
+
|
|
132
|
+
Reported subset: **500 images per class, 5,000 total**, drawn from
|
|
133
|
+
`alessiocorrado99/animals10`. Per class, the file list is sorted, shuffled with
|
|
134
|
+
`random.Random(<english class name>)`, and the first N images that decode
|
|
135
|
+
successfully are taken. Every file is opened, verified and RGB-converted before
|
|
136
|
+
selection; undecodable files are skipped and counted.
|
|
137
|
+
|
|
138
|
+
The seed depends only on the class name, never on N, so the tiers are **nested**:
|
|
139
|
+
`n10` is byte-identical to the first 10 images of `n100`, filenames included.
|
|
140
|
+
A comparison across tiers is therefore a genuine learning curve rather than
|
|
141
|
+
three unrelated samples.
|
|
142
|
+
|
|
143
|
+
Source folder names are Italian and are mapped to English (`cane`->`dog`,
|
|
144
|
+
`gatto`->`cat`, `ragno`->`spider`, ...). The per-class ceiling is set by the
|
|
145
|
+
smallest class, elephant, at 1,446 images.
|
|
146
|
+
|
|
147
|
+
**Grayscale - not yet added.**
|
|
148
|
+
|
|
149
|
+
## Results
|
|
150
|
+
|
|
151
|
+
Two runs over nested subsets of Animals-10, ten classes, RGB at 64x64. The
|
|
152
|
+
tiers share one fixed ordering, so `n100` is a byte-identical subset of
|
|
153
|
+
`n500` and reading across them is a learning curve rather than a comparison
|
|
154
|
+
of unrelated samples. Chance for ten classes is 0.100.
|
|
155
|
+
|
|
156
|
+
| Model | Macro F1 @ 100/class | Macro F1 @ 500/class | change |
|
|
157
|
+
|---|---|---|---|
|
|
158
|
+
| Simple CNN | 0.295 | **0.434** | +47% |
|
|
159
|
+
| SVM | 0.242 | 0.352 | +45% |
|
|
160
|
+
| Random Forest | 0.294 | 0.319 | +9% |
|
|
161
|
+
| Neural Network | 0.178 | 0.281 | +58% |
|
|
162
|
+
| Logistic Regression | 0.213 | 0.225 | +6% |
|
|
163
|
+
| Decision Tree | 0.093 | 0.180 | +94% |
|
|
164
|
+
|
|
165
|
+
At 100 images per class the CNN and Random Forest are tied. At 500 the CNN
|
|
166
|
+
leads by 36%, and Logistic Regression and Random Forest have nearly
|
|
167
|
+
flattened while the CNN and SVM are still climbing steeply. Reporting only
|
|
168
|
+
the smaller run would have supported the conclusion that a CNN and an
|
|
169
|
+
ensemble of trees are equivalent here - true at that size, and misleading as
|
|
170
|
+
a finding.
|
|
171
|
+
|
|
172
|
+
Cost at 500 per class tells a different story from accuracy alone:
|
|
173
|
+
|
|
174
|
+
| Model | Training | Inference |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| SVM | 547.4 s | 150.1 ms/image |
|
|
177
|
+
| Simple CNN | 44.8 s | 0.43 ms/image |
|
|
178
|
+
| Decision Tree | 20.2 s | 0.002 ms/image |
|
|
179
|
+
| Logistic Regression | 9.5 s | 0.040 ms/image |
|
|
180
|
+
| Random Forest | 3.3 s | 0.028 ms/image |
|
|
181
|
+
| Neural Network | 1.3 s | 0.025 ms/image |
|
|
182
|
+
|
|
183
|
+
The SVM buys third place at 75,000 times the Decision Tree's inference cost:
|
|
184
|
+
classifying a thousand images would take it two and a half minutes against
|
|
185
|
+
Random Forest's 0.03 seconds for a slightly better score.
|
|
186
|
+
|
|
187
|
+
Full outputs are under `benchmark_results/<tier>/`. Reproduce them with:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
python scripts/download_animals10.py --per-class 500
|
|
191
|
+
python scripts/run_benchmarks.py
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The 500/class run takes about 13 minutes, 9 of which are the SVM.
|
|
195
|
+
|
|
196
|
+
## Tests
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
pytest tests/
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`tests/fixtures/` holds small committed datasets so the suite runs in a clean
|
|
203
|
+
checkout with no downloads:
|
|
204
|
+
|
|
205
|
+
| Fixture | Contents |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `mini/` | 3 classes x 5 images, pristine. One image per required extension (`.jpeg`, `.jpg`, `.png`, `.bmp`, `.tiff`) at five different dimensions, so resizing and aspect handling are exercised. |
|
|
208
|
+
| `broken/` | 3 classes x 3 images plus one undecodable file and, in the manifests, one row pointing at a file that is not on disk. Covers the skip-and-report path. |
|
|
209
|
+
|
|
210
|
+
Each tree has matching `*_labels.csv`, `.json` and `.jsonl` manifests, so all
|
|
211
|
+
four dataset organizations can be tested against committed data.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT. See [LICENSE](LICENSE). The license covers this source code, not the
|
|
216
|
+
third-party image datasets it consumes.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Samuel_Collins_CV_Benchmarking"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Benchmark classical machine-learning and neural-network image classifiers through one public function."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Samuel Collins" }]
|
|
13
|
+
keywords = ["computer-vision", "image-classification", "benchmarking", "scikit-learn"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Image Recognition",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"numpy>=1.24",
|
|
22
|
+
"pandas>=2.0",
|
|
23
|
+
"pillow>=10.0",
|
|
24
|
+
"scikit-learn>=1.3",
|
|
25
|
+
"matplotlib>=3.7",
|
|
26
|
+
"torch>=2.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = ["pytest>=7.4", "build>=1.0", "twine>=4.0"]
|
|
31
|
+
# Only the report tooling in scripts/ needs these; the library itself does
|
|
32
|
+
# not, so they stay out of the default install.
|
|
33
|
+
report = ["markdown>=3.5"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/srcollins785/Samuel_Collins_CV_Benchmarking"
|
|
37
|
+
Repository = "https://github.com/srcollins785/Samuel_Collins_CV_Benchmarking"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
filterwarnings = [
|
|
45
|
+
# numpy 2.0 built against Apple's Accelerate BLAS raises these on matmul
|
|
46
|
+
# calls whose inputs and outputs are entirely finite. Verified spurious:
|
|
47
|
+
# a clean `a @ b` of random normals emits all three. Filtered narrowly so
|
|
48
|
+
# the suite output stays readable; NOT suppressed in the package itself,
|
|
49
|
+
# where a real overflow must still reach the user.
|
|
50
|
+
"ignore:divide by zero encountered in matmul:RuntimeWarning",
|
|
51
|
+
"ignore:overflow encountered in matmul:RuntimeWarning",
|
|
52
|
+
"ignore:invalid value encountered in matmul:RuntimeWarning",
|
|
53
|
+
]
|