datatunner 3.2.0__tar.gz → 3.2.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.
- {datatunner-3.2.0/datatunner.egg-info → datatunner-3.2.2}/PKG-INFO +1 -1
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/domain/experiment.py +2 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/orchestrator.py +110 -18
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/reporting/tables.py +20 -10
- {datatunner-3.2.0 → datatunner-3.2.2/datatunner.egg-info}/PKG-INFO +1 -1
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner.egg-info/SOURCES.txt +1 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/pyproject.toml +2 -2
- datatunner-3.2.2/tests/test_image_support.py +39 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_orchestrator_regressions.py +30 -1
- {datatunner-3.2.0 → datatunner-3.2.2}/LICENSE +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/MANIFEST.in +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/README.md +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/domain/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/domain/dataset.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/domain/generator.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/domain/metrics.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/performance/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/performance/classification.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/performance/regression.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/quality/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/quality/coverage.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/quality/fidelity.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/evaluation/quality/statistical.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/exceptions.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/generators/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/generators/augmentation.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/generators/base.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/generators/ctgan.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/generators/smote.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/infrastructure/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/infrastructure/hardware.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/infrastructure/logging.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/infrastructure/persistence.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/infrastructure/seed.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/mixing/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/mixing/engine.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/mixing/strategies.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/mixing/validators.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/optimization/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/optimization/base.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/optimization/bayesian.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/optimization/grid.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/optimization/random.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/py.typed +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/reporting/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/reporting/exporters.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/reporting/plots.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/training/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/training/checkpoint.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/training/environment.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner/training/runner.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner.egg-info/dependency_links.txt +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner.egg-info/requires.txt +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/datatunner.egg-info/top_level.txt +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_adult_ctgan.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_adult_smote.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_cifar_augmentation.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_colab_breast_cancer.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_colab_drybean_ctgan.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_colab_mnist_augmentation.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_colab_scatter_per_alpha.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/examples/example_comparison_smote_vs_ctgan.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/setup.cfg +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/__init__.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_aggregation.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_domain.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_generators.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_infrastructure.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_metrics_formatting.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_mixing.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_seed_counter.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_training_defaults.py +0 -0
- {datatunner-3.2.0 → datatunner-3.2.2}/tests/test_validators_coverage.py +0 -0
|
@@ -63,6 +63,7 @@ class ExperimentReport:
|
|
|
63
63
|
quality_results: List[Any] = field(default_factory=list)
|
|
64
64
|
optimization_results: List[ExperimentResult] = field(default_factory=list)
|
|
65
65
|
baseline_results: List[ExperimentResult] = field(default_factory=list)
|
|
66
|
+
paired_comparisons: List[Dict] = field(default_factory=list)
|
|
66
67
|
creation_timestamp: float = field(default_factory=time.time)
|
|
67
68
|
|
|
68
69
|
@property
|
|
@@ -97,6 +98,7 @@ class ExperimentReport:
|
|
|
97
98
|
'best_result': self.best_result.to_dict() if self.best_result else None,
|
|
98
99
|
'optimization_results': [r.to_dict() for r in self.optimization_results],
|
|
99
100
|
'baseline_results': [r.to_dict() for r in self.baseline_results],
|
|
101
|
+
'paired_comparisons': self.paired_comparisons,
|
|
100
102
|
'n_optimization_results': len(self.optimization_results),
|
|
101
103
|
'n_baseline_results': len(self.baseline_results),
|
|
102
104
|
'creation_timestamp': self.creation_timestamp,
|
|
@@ -168,21 +168,56 @@ class DataTunner:
|
|
|
168
168
|
seed_manager=self.seed_manager
|
|
169
169
|
)
|
|
170
170
|
|
|
171
|
-
# Phase 4: Run baselines
|
|
171
|
+
# Phase 4: Run baselines with a dedicated seed block (paired design)
|
|
172
172
|
baseline_results = []
|
|
173
|
+
paired_comparisons = []
|
|
173
174
|
if baselines:
|
|
174
|
-
print(f"[DataTunner] Running {len(baselines)} baseline comparisons...")
|
|
175
|
+
print(f"[DataTunner] Running {len(baselines)} baseline comparisons (paired, n={self.config.n_repetitions})...")
|
|
176
|
+
|
|
177
|
+
# Best point from the search grid — used for the paired re-run
|
|
178
|
+
best_alpha = None
|
|
179
|
+
if optimization_results:
|
|
180
|
+
target = self.config.target_metric
|
|
181
|
+
best_alpha = max(
|
|
182
|
+
optimization_results,
|
|
183
|
+
key=lambda r: r.performance.to_dict().get(target, -float('inf'))
|
|
184
|
+
).alpha
|
|
185
|
+
|
|
175
186
|
for name, alpha, base_gen in baselines:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
# ONE seed block per baseline; the same seeds are reused for
|
|
188
|
+
# alpha* so each pair shares the same random state (thesis
|
|
189
|
+
# Section 4.7 — "blocagem por semente").
|
|
190
|
+
seed_block = self.seed_manager.generate_seeds(self.config.n_repetitions)
|
|
191
|
+
|
|
192
|
+
base_results = self._run_baseline(name, alpha, base_gen, real_data, test_data,
|
|
193
|
+
seed_block=seed_block)
|
|
194
|
+
baseline_results.extend(base_results)
|
|
195
|
+
|
|
196
|
+
if best_alpha is not None and base_results:
|
|
197
|
+
star_results = self._run_paired_alpha_star(best_alpha, seed_block)
|
|
198
|
+
if star_results:
|
|
199
|
+
paired_comparisons.append({
|
|
200
|
+
'baseline_name': name,
|
|
201
|
+
'baseline_alpha': alpha,
|
|
202
|
+
'alpha_star': best_alpha,
|
|
203
|
+
'target_metric': self.config.target_metric,
|
|
204
|
+
'pairs': [
|
|
205
|
+
{
|
|
206
|
+
'seed': b.seed,
|
|
207
|
+
'baseline_metric': b.performance.to_dict().get(self.config.target_metric),
|
|
208
|
+
'alpha_star_metric': s.performance.to_dict().get(self.config.target_metric),
|
|
209
|
+
}
|
|
210
|
+
for b, s in zip(base_results, star_results)
|
|
211
|
+
],
|
|
212
|
+
})
|
|
179
213
|
|
|
180
214
|
# Phase 5: Compile report
|
|
181
215
|
report = ExperimentReport(
|
|
182
216
|
config=self.config,
|
|
183
217
|
quality_results=quality_results,
|
|
184
218
|
optimization_results=optimization_results,
|
|
185
|
-
baseline_results=baseline_results
|
|
219
|
+
baseline_results=baseline_results,
|
|
220
|
+
paired_comparisons=paired_comparisons,
|
|
186
221
|
)
|
|
187
222
|
|
|
188
223
|
# Save artifacts
|
|
@@ -201,6 +236,18 @@ class DataTunner:
|
|
|
201
236
|
|
|
202
237
|
return report
|
|
203
238
|
|
|
239
|
+
@staticmethod
|
|
240
|
+
def _count_samples(data: Any) -> int:
|
|
241
|
+
"""Number of samples in a dataset, handling (images, labels) tuples.
|
|
242
|
+
|
|
243
|
+
Image pipelines pass real_data as a (X, y) tuple; ``len()`` on the
|
|
244
|
+
tuple returns the number of elements (2), not the number of samples.
|
|
245
|
+
Without this, n_synthetic = int(alpha * 2) ≈ 0 for every alpha.
|
|
246
|
+
"""
|
|
247
|
+
if isinstance(data, tuple) and len(data) == 2:
|
|
248
|
+
return len(data[0])
|
|
249
|
+
return len(data)
|
|
250
|
+
|
|
204
251
|
def _run_single_experiment(self, alpha: float, seed: int) -> ExperimentResult:
|
|
205
252
|
"""Execute ONE complete experiment: generate -> mix -> train -> evaluate.
|
|
206
253
|
|
|
@@ -210,7 +257,7 @@ class DataTunner:
|
|
|
210
257
|
|
|
211
258
|
with self.environment.isolate(seed, experiment_id):
|
|
212
259
|
# 1. Generate synthetic data
|
|
213
|
-
n_real =
|
|
260
|
+
n_real = self._count_samples(self._real_data)
|
|
214
261
|
n_synthetic = int(alpha * n_real)
|
|
215
262
|
|
|
216
263
|
# Bind the generator RNG to this trial's seed so repetitions
|
|
@@ -311,7 +358,7 @@ class DataTunner:
|
|
|
311
358
|
) -> List[Dict]:
|
|
312
359
|
"""Pre-evaluate generator by generating a sample and measuring fidelity."""
|
|
313
360
|
# Generate a fixed-size sample for quality assessment
|
|
314
|
-
sample_size = min(1000,
|
|
361
|
+
sample_size = min(1000, self._count_samples(real_data))
|
|
315
362
|
generator.set_seed(generator.spec.random_state)
|
|
316
363
|
synthetic_sample = generator.generate(sample_size)
|
|
317
364
|
|
|
@@ -329,25 +376,70 @@ class DataTunner:
|
|
|
329
376
|
alpha: float,
|
|
330
377
|
generator: AbstractGenerator,
|
|
331
378
|
real_data: Any,
|
|
332
|
-
test_data: Any
|
|
333
|
-
|
|
334
|
-
|
|
379
|
+
test_data: Any,
|
|
380
|
+
seed_block: Optional[List[int]] = None
|
|
381
|
+
) -> List[ExperimentResult]:
|
|
382
|
+
"""Run baseline trials over a dedicated seed block.
|
|
383
|
+
|
|
384
|
+
The block is generated ONCE per baseline (Section 4.7 of the thesis:
|
|
385
|
+
"blocagem por semente") and the SAME seeds are later reused to
|
|
386
|
+
re-run alpha* — that pairing is what legitimizes the Wilcoxon
|
|
387
|
+
signed-rank test of P1/H1. Earlier versions ran a single trial with
|
|
388
|
+
one fresh seed, which made a paired comparison impossible.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
name: Baseline label.
|
|
392
|
+
alpha: Proportion to evaluate (usually 0.0).
|
|
393
|
+
generator: Generator instance for this baseline.
|
|
394
|
+
real_data: Training data.
|
|
395
|
+
test_data: Hold-out test data.
|
|
396
|
+
seed_block: Optional pre-generated block of seeds. If None, a
|
|
397
|
+
fresh block of ``config.n_repetitions`` seeds is
|
|
398
|
+
drawn from the counter.
|
|
399
|
+
"""
|
|
335
400
|
old_generator = self._generator
|
|
401
|
+
results: List[ExperimentResult] = []
|
|
336
402
|
try:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
seed = self.seed_manager.generate_seeds(1)[0]
|
|
403
|
+
if seed_block is None:
|
|
404
|
+
seed_block = self.seed_manager.generate_seeds(self.config.n_repetitions)
|
|
340
405
|
|
|
341
406
|
# Temporarily swap generator
|
|
342
407
|
self._generator = generator
|
|
343
408
|
generator.fit(real_data)
|
|
344
409
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
410
|
+
for seed in seed_block:
|
|
411
|
+
try:
|
|
412
|
+
result = self._run_single_experiment(alpha, seed)
|
|
413
|
+
if result is not None:
|
|
414
|
+
results.append(result)
|
|
415
|
+
except Exception as e:
|
|
416
|
+
print(f"[DataTunner] Baseline '{name}' trial seed={seed} failed: {e}")
|
|
417
|
+
return results
|
|
348
418
|
except Exception as e:
|
|
349
419
|
print(f"[DataTunner] Baseline '{name}' failed: {e}")
|
|
350
|
-
return
|
|
420
|
+
return results
|
|
351
421
|
finally:
|
|
352
422
|
# Always restore the main generator, even on failure.
|
|
353
423
|
self._generator = old_generator
|
|
424
|
+
|
|
425
|
+
def _run_paired_alpha_star(
|
|
426
|
+
self,
|
|
427
|
+
alpha_star: float,
|
|
428
|
+
seed_block: List[int]
|
|
429
|
+
) -> List[ExperimentResult]:
|
|
430
|
+
"""Re-run alpha* with the SAME seed block used by a baseline.
|
|
431
|
+
|
|
432
|
+
This is the second half of the paired design: for each seed in the
|
|
433
|
+
block, one trial at alpha* is executed with the main generator. The
|
|
434
|
+
caller zips these results with the baseline results (same seed
|
|
435
|
+
order) to build the paired comparison for the Wilcoxon test.
|
|
436
|
+
"""
|
|
437
|
+
results: List[ExperimentResult] = []
|
|
438
|
+
for seed in seed_block:
|
|
439
|
+
try:
|
|
440
|
+
result = self._run_single_experiment(alpha_star, seed)
|
|
441
|
+
if result is not None:
|
|
442
|
+
results.append(result)
|
|
443
|
+
except Exception as e:
|
|
444
|
+
print(f"[DataTunner] Paired alpha* trial seed={seed} failed: {e}")
|
|
445
|
+
return results
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Publication-ready table generation for experiment reports."""
|
|
2
2
|
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
4
4
|
|
|
5
|
+
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
6
7
|
|
|
7
8
|
from datatunner.domain.experiment import ExperimentReport, ExperimentResult
|
|
@@ -59,20 +60,29 @@ class TableGenerator:
|
|
|
59
60
|
"type": "optimization",
|
|
60
61
|
})
|
|
61
62
|
|
|
63
|
+
# Baselines: aggregate the n_r trials of the paired block into one
|
|
64
|
+
# row per (method, alpha) so the comparison table stays readable.
|
|
65
|
+
baseline_agg: Dict[Tuple[str, float], List[float]] = {}
|
|
62
66
|
for r in report.baseline_results:
|
|
63
67
|
perf = r.performance.to_dict()
|
|
64
68
|
value = perf.get(target)
|
|
69
|
+
method = (
|
|
70
|
+
r.generator_spec.name
|
|
71
|
+
if hasattr(r.generator_spec, "name")
|
|
72
|
+
else str(r.generator_spec)
|
|
73
|
+
)
|
|
74
|
+
if value is not None:
|
|
75
|
+
baseline_agg.setdefault((method, r.alpha), []).append(value)
|
|
76
|
+
|
|
77
|
+
for (method, alpha), values in baseline_agg.items():
|
|
78
|
+
mean = float(np.mean(values))
|
|
65
79
|
improvement = None
|
|
66
|
-
if best_value is not None and
|
|
67
|
-
improvement = ((
|
|
80
|
+
if best_value is not None and best_value != 0:
|
|
81
|
+
improvement = ((mean - best_value) / best_value) * 100.0
|
|
68
82
|
rows.append({
|
|
69
|
-
"method":
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
else str(r.generator_spec)
|
|
73
|
-
),
|
|
74
|
-
"alpha": r.alpha,
|
|
75
|
-
target: f"{value:.4f}" if value is not None else "N/A",
|
|
83
|
+
"method": method,
|
|
84
|
+
"alpha": alpha,
|
|
85
|
+
target: f"{mean:.4f}",
|
|
76
86
|
"improvement_%": (
|
|
77
87
|
f"{improvement:+.2f}%" if improvement is not None else "N/A"
|
|
78
88
|
),
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datatunner"
|
|
7
|
-
# Bump on release: the current published version on PyPI is 3.
|
|
8
|
-
version = "3.2.
|
|
7
|
+
# Bump on release: the current published version on PyPI is 3.2.1.
|
|
8
|
+
version = "3.2.2"
|
|
9
9
|
description = "DataTunner: Scientific Platform for Optimal Artificial Data Proportion in Deep Learning"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = "MIT"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Regression tests for image pipeline support (X, y) tuples.
|
|
2
|
+
|
|
3
|
+
Bug fix (2026-08): `_run_single_experiment` computed n_synthetic with
|
|
4
|
+
``len(real_data)``. For image pipelines real_data is a (X, y) tuple, so
|
|
5
|
+
len() returned 2 → n_synthetic = int(alpha * 2) ≈ 0 for every alpha:
|
|
6
|
+
the ImageAugmentation pipeline generated (almost) no synthetic data.
|
|
7
|
+
|
|
8
|
+
Fix: DataTunner._count_samples() counts rows of data[0] for 2-tuples.
|
|
9
|
+
Used in _run_single_experiment and _evaluate_generator_quality.
|
|
10
|
+
|
|
11
|
+
Run where deps are installed (CI / Colab): pytest tests/test_image_support.py
|
|
12
|
+
"""
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from datatunner.orchestrator import DataTunner
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestCountSamples:
|
|
19
|
+
@pytest.mark.parametrize("n", [0, 1, 2, 10, 1000])
|
|
20
|
+
def test_tuple_counts_samples_not_elements(self, n):
|
|
21
|
+
X = [0] * n
|
|
22
|
+
y = [0] * n
|
|
23
|
+
assert DataTunner._count_samples((X, y)) == n
|
|
24
|
+
|
|
25
|
+
def test_nested_2d_like_image_array(self):
|
|
26
|
+
# X shaped (N, H, W): must count N, not dimensions
|
|
27
|
+
X = [[[0] * 4 for _ in range(4)] for _ in range(250)]
|
|
28
|
+
y = [0] * 250
|
|
29
|
+
assert DataTunner._count_samples((X, y)) == 250
|
|
30
|
+
|
|
31
|
+
def test_plain_list_counts_len(self):
|
|
32
|
+
assert DataTunner._count_samples([1, 2, 3, 4]) == 4
|
|
33
|
+
|
|
34
|
+
def test_single_element_list(self):
|
|
35
|
+
assert DataTunner._count_samples(["only"]) == 1
|
|
36
|
+
|
|
37
|
+
def test_non_tuple_pair_not_misclassified(self):
|
|
38
|
+
# A 2-element tuple is ambiguous; contract: (X, y) tuples mean images
|
|
39
|
+
assert DataTunner._count_samples((1, 2)) == 1 # len(data[0]) = 1
|
|
@@ -116,7 +116,7 @@ class TestBaselineIsolation:
|
|
|
116
116
|
|
|
117
117
|
def test_baseline_uses_fresh_seed(self):
|
|
118
118
|
spec = GeneratorSpec(name="stub", mechanism="smote", hyperparameters={}, random_state=42)
|
|
119
|
-
tunner = _make_tunner()
|
|
119
|
+
tunner = _make_tunner() # n_repetitions=1
|
|
120
120
|
baseline_gen = _StubGenerator(spec)
|
|
121
121
|
|
|
122
122
|
report = _simple_run(tunner, _StubGenerator(spec), baselines=[("base", 0.0, baseline_gen)])
|
|
@@ -125,3 +125,32 @@ class TestBaselineIsolation:
|
|
|
125
125
|
baseline_result = report.baseline_results[0]
|
|
126
126
|
# Regression: baselines reused the first grid seed
|
|
127
127
|
assert baseline_result.seed != grid_seed
|
|
128
|
+
|
|
129
|
+
def test_baseline_paired_block_shares_seed_with_alpha_star(self):
|
|
130
|
+
"""Paired design (thesis 4.7): baseline and alpha* share a seed block."""
|
|
131
|
+
config = ExperimentConfig(
|
|
132
|
+
data_type="tabular",
|
|
133
|
+
search_strategy=GridSearch(),
|
|
134
|
+
alpha_bounds=(0.0, 1.0),
|
|
135
|
+
n_repetitions=3,
|
|
136
|
+
search_budget=3,
|
|
137
|
+
target_metric="f1_macro",
|
|
138
|
+
)
|
|
139
|
+
spec = GeneratorSpec(name="stub", mechanism="smote", hyperparameters={}, random_state=42)
|
|
140
|
+
tunner = _make_tunner(config)
|
|
141
|
+
baseline_gen = _StubGenerator(spec)
|
|
142
|
+
|
|
143
|
+
report = _simple_run(tunner, _StubGenerator(spec), baselines=[("base", 0.0, baseline_gen)])
|
|
144
|
+
|
|
145
|
+
# Baseline now runs n_repetitions trials (one per seed in the block)
|
|
146
|
+
assert len(report.baseline_results) == config.n_repetitions
|
|
147
|
+
# Paired comparison: baseline seeds == alpha* re-run seeds
|
|
148
|
+
assert report.paired_comparisons, "paired_comparisons should be populated"
|
|
149
|
+
pc = report.paired_comparisons[0]
|
|
150
|
+
assert len(pc["pairs"]) == config.n_repetitions
|
|
151
|
+
seeds = [p["seed"] for p in pc["pairs"]]
|
|
152
|
+
assert len(set(seeds)) == config.n_repetitions
|
|
153
|
+
assert pc["alpha_star"] == report.best_result.alpha
|
|
154
|
+
# Every pair has both metrics
|
|
155
|
+
for p in pc["pairs"]:
|
|
156
|
+
assert "baseline_metric" in p and "alpha_star_metric" in p
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|