trustbench 0.2.0__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.
- trustbench/__init__.py +121 -0
- trustbench/api/__init__.py +5 -0
- trustbench/api/app.py +45 -0
- trustbench/api/routes/experiments.py +82 -0
- trustbench/benchmark.py +264 -0
- trustbench/cli/__init__.py +5 -0
- trustbench/cli/main.py +303 -0
- trustbench/config/__init__.py +22 -0
- trustbench/config/loader.py +71 -0
- trustbench/config/schema.py +149 -0
- trustbench/core/__init__.py +30 -0
- trustbench/core/evaluator.py +163 -0
- trustbench/core/exceptions.py +17 -0
- trustbench/core/inference.py +137 -0
- trustbench/core/protocols.py +57 -0
- trustbench/core/reproducibility.py +74 -0
- trustbench/core/results.py +56 -0
- trustbench/datasets/__init__.py +23 -0
- trustbench/datasets/base.py +31 -0
- trustbench/datasets/cifar10.py +164 -0
- trustbench/datasets/memory.py +49 -0
- trustbench/datasets/torch_dataset.py +108 -0
- trustbench/evaluation/__init__.py +164 -0
- trustbench/evaluation/artifacts.py +13 -0
- trustbench/evaluation/calibration.py +251 -0
- trustbench/evaluation/classification.py +145 -0
- trustbench/evaluation/distribution_shift.py +269 -0
- trustbench/evaluation/error_detection.py +281 -0
- trustbench/evaluation/fairness.py +103 -0
- trustbench/evaluation/inference.py +16 -0
- trustbench/evaluation/regression.py +119 -0
- trustbench/evaluation/reproducibility.py +13 -0
- trustbench/evaluation/retrieval.py +76 -0
- trustbench/evaluation/runner.py +99 -0
- trustbench/evaluation/segmentation.py +104 -0
- trustbench/evaluation/selective.py +221 -0
- trustbench/evaluation/streaming.py +237 -0
- trustbench/evaluation/uncertainty.py +341 -0
- trustbench/experiments/__init__.py +48 -0
- trustbench/experiments/comparison.py +284 -0
- trustbench/experiments/lineage.py +234 -0
- trustbench/experiments/manager.py +387 -0
- trustbench/experiments/registry.py +184 -0
- trustbench/experiments/schemas.py +78 -0
- trustbench/metrics/__init__.py +45 -0
- trustbench/metrics/calibration.py +15 -0
- trustbench/metrics/classification.py +11 -0
- trustbench/metrics/error_detection.py +17 -0
- trustbench/metrics/regression.py +96 -0
- trustbench/metrics/retrieval.py +108 -0
- trustbench/metrics/segmentation.py +116 -0
- trustbench/metrics/selective.py +17 -0
- trustbench/metrics/uncertainty.py +23 -0
- trustbench/models/__init__.py +74 -0
- trustbench/models/base.py +48 -0
- trustbench/models/checkpoint.py +98 -0
- trustbench/models/densenet.py +100 -0
- trustbench/models/dla.py +124 -0
- trustbench/models/dla_simple.py +117 -0
- trustbench/models/dpn.py +90 -0
- trustbench/models/efficientnet.py +165 -0
- trustbench/models/googlenet.py +98 -0
- trustbench/models/jax.py +95 -0
- trustbench/models/keras.py +105 -0
- trustbench/models/lenet.py +24 -0
- trustbench/models/mobilenet.py +53 -0
- trustbench/models/mobilenetv2.py +79 -0
- trustbench/models/onnx.py +134 -0
- trustbench/models/pnasnet.py +118 -0
- trustbench/models/preact_resnet.py +110 -0
- trustbench/models/pytorch.py +208 -0
- trustbench/models/regnet.py +143 -0
- trustbench/models/resnet.py +124 -0
- trustbench/models/resnext.py +95 -0
- trustbench/models/senet.py +113 -0
- trustbench/models/shufflenet.py +101 -0
- trustbench/models/shufflenetv2.py +153 -0
- trustbench/models/sklearn.py +141 -0
- trustbench/models/vgg.py +37 -0
- trustbench/registry/__init__.py +77 -0
- trustbench/registry/corruptions.py +181 -0
- trustbench/registry/datasets.py +192 -0
- trustbench/registry/metrics.py +244 -0
- trustbench/registry/models.py +212 -0
- trustbench/registry/plugins.py +222 -0
- trustbench/reporting/__init__.py +57 -0
- trustbench/reporting/artifacts.py +131 -0
- trustbench/reporting/hashing.py +21 -0
- trustbench/reporting/html_report.py +717 -0
- trustbench/reporting/plots.py +549 -0
- trustbench/reporting/result_io.py +109 -0
- trustbench/reporting/schema.py +195 -0
- trustbench/reporting/tables.py +64 -0
- trustbench/robustness/__init__.py +70 -0
- trustbench/robustness/aggregation.py +159 -0
- trustbench/robustness/corruptions.py +426 -0
- trustbench/robustness/evaluator.py +313 -0
- trustbench/robustness/report.py +11 -0
- trustbench/robustness/severity.py +525 -0
- trustbench/storage/__init__.py +66 -0
- trustbench/storage/base.py +171 -0
- trustbench/storage/database.py +67 -0
- trustbench/storage/filesystem.py +489 -0
- trustbench/storage/models.py +55 -0
- trustbench/storage/repositories.py +168 -0
- trustbench/storage/sqlite.py +470 -0
- trustbench-0.2.0.dist-info/METADATA +296 -0
- trustbench-0.2.0.dist-info/RECORD +110 -0
- trustbench-0.2.0.dist-info/WHEEL +4 -0
- trustbench-0.2.0.dist-info/entry_points.txt +3 -0
trustbench/__init__.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""TrustBench: Evaluation framework for machine learning model reliability."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
|
|
5
|
+
from trustbench.benchmark import TrustBench
|
|
6
|
+
from trustbench.core.evaluator import evaluate
|
|
7
|
+
from trustbench.core.exceptions import (
|
|
8
|
+
SecurityError,
|
|
9
|
+
ShapeError,
|
|
10
|
+
TrustBenchError,
|
|
11
|
+
UnsupportedCapabilityError,
|
|
12
|
+
)
|
|
13
|
+
from trustbench.core.protocols import DatasetAdapter, ModelAdapter
|
|
14
|
+
from trustbench.core.results import EvaluationResult
|
|
15
|
+
from trustbench.evaluation.distribution_shift import (
|
|
16
|
+
ShiftType,
|
|
17
|
+
compute_label_distribution_shift,
|
|
18
|
+
compute_ood_detection_metrics,
|
|
19
|
+
evaluate_distribution_shift,
|
|
20
|
+
)
|
|
21
|
+
from trustbench.evaluation.fairness import compute_fairness_metrics
|
|
22
|
+
from trustbench.evaluation.regression import RegressionResult, evaluate_regression
|
|
23
|
+
from trustbench.evaluation.retrieval import RetrievalResult, evaluate_retrieval
|
|
24
|
+
from trustbench.evaluation.segmentation import (
|
|
25
|
+
SegmentationResult,
|
|
26
|
+
evaluate_segmentation,
|
|
27
|
+
)
|
|
28
|
+
from trustbench.metrics.regression import compute_regression_metrics
|
|
29
|
+
from trustbench.metrics.retrieval import compute_retrieval_metrics
|
|
30
|
+
from trustbench.metrics.segmentation import compute_segmentation_metrics
|
|
31
|
+
from trustbench.registry import (
|
|
32
|
+
GenericDatasetAdapter,
|
|
33
|
+
GenericModelAdapter,
|
|
34
|
+
discover_plugins,
|
|
35
|
+
list_registered_corruptions,
|
|
36
|
+
list_registered_metrics,
|
|
37
|
+
load_plugin,
|
|
38
|
+
register_corruption,
|
|
39
|
+
register_dataset_adapter,
|
|
40
|
+
register_metric,
|
|
41
|
+
register_model_adapter,
|
|
42
|
+
validate_dataset_adapter,
|
|
43
|
+
validate_model_adapter,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"DatasetAdapter",
|
|
48
|
+
"EvaluationResult",
|
|
49
|
+
"GenericDatasetAdapter",
|
|
50
|
+
"GenericModelAdapter",
|
|
51
|
+
"JAXModelAdapter",
|
|
52
|
+
"KerasModelAdapter",
|
|
53
|
+
"ModelAdapter",
|
|
54
|
+
"ONNXModelAdapter",
|
|
55
|
+
"PyTorchModelAdapter",
|
|
56
|
+
"RegressionResult",
|
|
57
|
+
"RetrievalResult",
|
|
58
|
+
"SecurityError",
|
|
59
|
+
"SegmentationResult",
|
|
60
|
+
"ShapeError",
|
|
61
|
+
"ShiftType",
|
|
62
|
+
"SklearnModelAdapter",
|
|
63
|
+
"TrustBench",
|
|
64
|
+
"TrustBenchError",
|
|
65
|
+
"UnsupportedCapabilityError",
|
|
66
|
+
"__version__",
|
|
67
|
+
"compute_fairness_metrics",
|
|
68
|
+
"compute_label_distribution_shift",
|
|
69
|
+
"compute_ood_detection_metrics",
|
|
70
|
+
"compute_regression_metrics",
|
|
71
|
+
"compute_retrieval_metrics",
|
|
72
|
+
"compute_segmentation_metrics",
|
|
73
|
+
"discover_plugins",
|
|
74
|
+
"evaluate",
|
|
75
|
+
"evaluate_distribution_shift",
|
|
76
|
+
"evaluate_regression",
|
|
77
|
+
"evaluate_retrieval",
|
|
78
|
+
"evaluate_segmentation",
|
|
79
|
+
"list_registered_corruptions",
|
|
80
|
+
"list_registered_metrics",
|
|
81
|
+
"load_plugin",
|
|
82
|
+
"register_corruption",
|
|
83
|
+
"register_dataset_adapter",
|
|
84
|
+
"register_metric",
|
|
85
|
+
"register_model_adapter",
|
|
86
|
+
"validate_dataset_adapter",
|
|
87
|
+
"validate_model_adapter",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def __getattr__(name: str):
|
|
92
|
+
if name == "ExperimentManager":
|
|
93
|
+
from trustbench.experiments.manager import ExperimentManager
|
|
94
|
+
|
|
95
|
+
return ExperimentManager
|
|
96
|
+
if name in ("FilesystemExperimentStore", "SQLiteExperimentStore"):
|
|
97
|
+
from trustbench import storage
|
|
98
|
+
|
|
99
|
+
return getattr(storage, name)
|
|
100
|
+
if name == "SklearnModelAdapter":
|
|
101
|
+
from trustbench.models.sklearn import SklearnModelAdapter
|
|
102
|
+
|
|
103
|
+
return SklearnModelAdapter
|
|
104
|
+
if name == "PyTorchModelAdapter":
|
|
105
|
+
from trustbench.models.pytorch import PyTorchModelAdapter
|
|
106
|
+
|
|
107
|
+
return PyTorchModelAdapter
|
|
108
|
+
if name == "ONNXModelAdapter":
|
|
109
|
+
from trustbench.models.onnx import ONNXModelAdapter
|
|
110
|
+
|
|
111
|
+
return ONNXModelAdapter
|
|
112
|
+
if name == "KerasModelAdapter":
|
|
113
|
+
from trustbench.models.keras import KerasModelAdapter
|
|
114
|
+
|
|
115
|
+
return KerasModelAdapter
|
|
116
|
+
if name == "JAXModelAdapter":
|
|
117
|
+
from trustbench.models.jax import JAXModelAdapter
|
|
118
|
+
|
|
119
|
+
return JAXModelAdapter
|
|
120
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
121
|
+
|
trustbench/api/app.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""FastAPI application factory for TrustBench API."""
|
|
2
|
+
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
+
|
|
8
|
+
from trustbench.api.routes.experiments import router as experiments_router
|
|
9
|
+
from trustbench.storage.database import init_db
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@asynccontextmanager
|
|
13
|
+
async def lifespan(app: FastAPI):
|
|
14
|
+
# Initialize database tables on startup
|
|
15
|
+
init_db()
|
|
16
|
+
yield
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_app() -> FastAPI:
|
|
20
|
+
"""Create and configure TrustBench FastAPI application."""
|
|
21
|
+
app = FastAPI(
|
|
22
|
+
title="TrustBench API",
|
|
23
|
+
description="ML Reliability, Robustness & Uncertainty Evaluation Framework API",
|
|
24
|
+
version="0.1.0",
|
|
25
|
+
lifespan=lifespan,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app.add_middleware(
|
|
29
|
+
CORSMiddleware,
|
|
30
|
+
allow_origins=["*"],
|
|
31
|
+
allow_credentials=True,
|
|
32
|
+
allow_methods=["*"],
|
|
33
|
+
allow_headers=["*"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
app.include_router(experiments_router)
|
|
37
|
+
|
|
38
|
+
@app.get("/health", tags=["health"])
|
|
39
|
+
def health_check():
|
|
40
|
+
return {"status": "ok", "service": "trustbench-api"}
|
|
41
|
+
|
|
42
|
+
return app
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
app = create_app()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""FastAPI routes for experiment management, metrics querying, artifacts, and comparisons."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
4
|
+
from sqlalchemy.orm import Session
|
|
5
|
+
|
|
6
|
+
from trustbench.experiments.manager import ExperimentManager
|
|
7
|
+
from trustbench.experiments.schemas import (
|
|
8
|
+
ExperimentComparisonRequest,
|
|
9
|
+
ExperimentComparisonResponse,
|
|
10
|
+
ExperimentDetail,
|
|
11
|
+
ExperimentResponse,
|
|
12
|
+
)
|
|
13
|
+
from trustbench.storage.database import get_db_session
|
|
14
|
+
|
|
15
|
+
router = APIRouter(prefix="/experiments", tags=["experiments"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_manager(session: Session = Depends(get_db_session)) -> ExperimentManager:
|
|
19
|
+
"""Dependency for ExperimentManager."""
|
|
20
|
+
return ExperimentManager(session=session)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.get("", response_model=list[ExperimentResponse])
|
|
24
|
+
def list_experiments(
|
|
25
|
+
experiment_type: str | None = Query(None, description="Filter by experiment type (e.g. baseline, robustness)"),
|
|
26
|
+
status: str | None = Query(None, description="Filter by status (pending, running, completed, failed)"),
|
|
27
|
+
checkpoint_sha256: str | None = Query(None, description="Filter by checkpoint SHA-256 hash"),
|
|
28
|
+
manager: ExperimentManager = Depends(get_manager),
|
|
29
|
+
):
|
|
30
|
+
"""List all registered experiments."""
|
|
31
|
+
return manager.list_experiments(
|
|
32
|
+
experiment_type=experiment_type,
|
|
33
|
+
status=status,
|
|
34
|
+
checkpoint_sha256=checkpoint_sha256,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.get("/{experiment_id}", response_model=ExperimentDetail)
|
|
39
|
+
def get_experiment_detail(
|
|
40
|
+
experiment_id: str,
|
|
41
|
+
manager: ExperimentManager = Depends(get_manager),
|
|
42
|
+
):
|
|
43
|
+
"""Get full details of a specific experiment."""
|
|
44
|
+
exp = manager.get_experiment_detail(experiment_id)
|
|
45
|
+
if not exp:
|
|
46
|
+
raise HTTPException(status_code=404, detail=f"Experiment '{experiment_id}' not found.")
|
|
47
|
+
return exp
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.post("/compare", response_model=ExperimentComparisonResponse)
|
|
51
|
+
def compare_experiments(
|
|
52
|
+
req: ExperimentComparisonRequest,
|
|
53
|
+
manager: ExperimentManager = Depends(get_manager),
|
|
54
|
+
):
|
|
55
|
+
"""Compare multiple experiments across shared conditions and metrics."""
|
|
56
|
+
result = manager.compare_experiments(
|
|
57
|
+
experiment_ids=req.experiment_ids,
|
|
58
|
+
conditions=req.conditions,
|
|
59
|
+
metrics=req.metrics,
|
|
60
|
+
)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@router.post("/index-directory")
|
|
65
|
+
def index_artifact_directory(
|
|
66
|
+
directory_path: str = Query(..., description="Path to on-disk artifact directory"),
|
|
67
|
+
experiment_name: str | None = Query(None, description="Optional custom name"),
|
|
68
|
+
manager: ExperimentManager = Depends(get_manager),
|
|
69
|
+
):
|
|
70
|
+
"""Index an on-disk artifact folder into the database registry."""
|
|
71
|
+
try:
|
|
72
|
+
model = manager.registry.index_artifact_directory(directory_path, experiment_name=experiment_name)
|
|
73
|
+
return {
|
|
74
|
+
"message": "Experiment indexed successfully.",
|
|
75
|
+
"experiment_id": model.id,
|
|
76
|
+
"name": model.name,
|
|
77
|
+
"status": model.status,
|
|
78
|
+
}
|
|
79
|
+
except FileNotFoundError as e:
|
|
80
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
81
|
+
except (ValueError, RuntimeError, OSError) as e:
|
|
82
|
+
raise HTTPException(status_code=500, detail=str(e))
|
trustbench/benchmark.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""TrustBench — public high-level API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from trustbench.core.evaluator import evaluate as _core_evaluate
|
|
9
|
+
from trustbench.core.protocols import DatasetAdapter, ModelAdapter
|
|
10
|
+
from trustbench.core.results import EvaluationResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _wrap_model(model: Any, **kwargs: Any) -> ModelAdapter:
|
|
14
|
+
"""Coerce *model* to a :class:`ModelAdapter`."""
|
|
15
|
+
# Already a ModelAdapter — use directly
|
|
16
|
+
if isinstance(model, ModelAdapter):
|
|
17
|
+
return model
|
|
18
|
+
|
|
19
|
+
# torch.nn.Module — wrap in PyTorchModelAdapter
|
|
20
|
+
try:
|
|
21
|
+
from torch import nn
|
|
22
|
+
|
|
23
|
+
from trustbench.models.pytorch import PyTorchModelAdapter
|
|
24
|
+
except ImportError as exc:
|
|
25
|
+
raise ImportError(
|
|
26
|
+
"PyTorch is required to auto-wrap an nn.Module. "
|
|
27
|
+
"Install it with: pip install torch"
|
|
28
|
+
) from exc
|
|
29
|
+
|
|
30
|
+
if isinstance(model, nn.Module):
|
|
31
|
+
num_classes = kwargs.get("num_classes")
|
|
32
|
+
if num_classes is None:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
"Pass num_classes=<int> when providing a raw nn.Module to TrustBench."
|
|
35
|
+
)
|
|
36
|
+
device = kwargs.get("device")
|
|
37
|
+
return PyTorchModelAdapter(model, num_classes=num_classes, device=device)
|
|
38
|
+
|
|
39
|
+
# Scikit-learn estimator
|
|
40
|
+
if hasattr(model, "predict") and hasattr(model, "fit"):
|
|
41
|
+
from trustbench.models.sklearn import SklearnModelAdapter
|
|
42
|
+
|
|
43
|
+
task = kwargs.get("task", "classification")
|
|
44
|
+
num_classes = kwargs.get("num_classes")
|
|
45
|
+
return SklearnModelAdapter(model, num_classes=num_classes, task=task)
|
|
46
|
+
|
|
47
|
+
# ONNX InferenceSession
|
|
48
|
+
if hasattr(model, "run") and hasattr(model, "get_inputs"):
|
|
49
|
+
from trustbench.models.onnx import ONNXModelAdapter
|
|
50
|
+
|
|
51
|
+
num_classes = kwargs.get("num_classes", 2)
|
|
52
|
+
task = kwargs.get("task", "classification")
|
|
53
|
+
return ONNXModelAdapter(model, num_classes=num_classes, task=task)
|
|
54
|
+
|
|
55
|
+
raise TypeError(
|
|
56
|
+
f"model must be a ModelAdapter, nn.Module, scikit-learn estimator, or ONNX session, got {type(model).__name__}."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _wrap_dataset(dataset: Any) -> DatasetAdapter:
|
|
61
|
+
"""Coerce *dataset* to a :class:`DatasetAdapter`."""
|
|
62
|
+
if isinstance(dataset, DatasetAdapter):
|
|
63
|
+
return dataset
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
from torch.utils.data import DataLoader, Dataset
|
|
67
|
+
|
|
68
|
+
from trustbench.datasets.torch_dataset import TorchDatasetAdapter
|
|
69
|
+
except ImportError as exc:
|
|
70
|
+
raise ImportError(
|
|
71
|
+
"PyTorch is required to auto-wrap a Dataset/DataLoader. "
|
|
72
|
+
"Install it with: pip install torch"
|
|
73
|
+
) from exc
|
|
74
|
+
|
|
75
|
+
if isinstance(dataset, DataLoader):
|
|
76
|
+
# Extract the underlying Dataset from the DataLoader
|
|
77
|
+
dataset = dataset.dataset # type: ignore[assignment]
|
|
78
|
+
|
|
79
|
+
if isinstance(dataset, Dataset):
|
|
80
|
+
return TorchDatasetAdapter(dataset)
|
|
81
|
+
|
|
82
|
+
raise TypeError(
|
|
83
|
+
f"dataset must be a DatasetAdapter, torch Dataset, or DataLoader, "
|
|
84
|
+
f"got {type(dataset).__name__}."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TrustBench:
|
|
89
|
+
"""High-level evaluation interface.
|
|
90
|
+
|
|
91
|
+
Example::
|
|
92
|
+
|
|
93
|
+
import torch.nn as nn
|
|
94
|
+
from trustbench import TrustBench
|
|
95
|
+
|
|
96
|
+
model = nn.Linear(4, 3)
|
|
97
|
+
# dataset is any torch.utils.data.Dataset or DatasetAdapter
|
|
98
|
+
tb = TrustBench(model, dataset, num_classes=3)
|
|
99
|
+
result = tb.evaluate()
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
model: A :class:`~trustbench.core.protocols.ModelAdapter`, ``nn.Module``
|
|
103
|
+
(requires *num_classes* kwarg), DataLoader, or Dataset.
|
|
104
|
+
dataset: A :class:`~trustbench.core.protocols.DatasetAdapter`,
|
|
105
|
+
``torch.utils.data.Dataset``, or ``torch.utils.data.DataLoader``.
|
|
106
|
+
metrics: Metric group names to compute. Defaults to
|
|
107
|
+
``("classification", "calibration", "uncertainty")``.
|
|
108
|
+
batch_size: Inference batch size.
|
|
109
|
+
class_names: Optional list of human-readable class labels.
|
|
110
|
+
seed: Global random seed.
|
|
111
|
+
output_dir: If provided, artifacts are saved here after :meth:`evaluate`.
|
|
112
|
+
num_classes: Required when *model* is a raw ``nn.Module``.
|
|
113
|
+
device: Device string passed to :class:`PyTorchModelAdapter`.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
model: Any,
|
|
119
|
+
dataset: Any,
|
|
120
|
+
*,
|
|
121
|
+
metrics: list[str] | tuple[str, ...] | None = None,
|
|
122
|
+
batch_size: int = 100,
|
|
123
|
+
class_names: list[str] | tuple[str, ...] | None = None,
|
|
124
|
+
seed: int | None = None,
|
|
125
|
+
output_dir: str | Path | None = None,
|
|
126
|
+
# forwarded to _wrap_model when model is nn.Module
|
|
127
|
+
num_classes: int | None = None,
|
|
128
|
+
device: str | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
self._model_adapter: ModelAdapter = _wrap_model(
|
|
131
|
+
model, num_classes=num_classes, device=device
|
|
132
|
+
)
|
|
133
|
+
self._dataset_adapter: DatasetAdapter = _wrap_dataset(dataset)
|
|
134
|
+
self._metrics = metrics
|
|
135
|
+
self._batch_size = batch_size
|
|
136
|
+
self._class_names = class_names
|
|
137
|
+
self._seed = seed
|
|
138
|
+
self._output_dir = Path(output_dir) if output_dir is not None else None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Class-method constructor from config
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def from_config(cls, config_path: str | Path) -> TrustBench:
|
|
146
|
+
"""Construct a :class:`TrustBench` instance from a YAML config file.
|
|
147
|
+
|
|
148
|
+
The *model* and *dataset* must be resolvable from the config (i.e. a
|
|
149
|
+
checkpoint path and architecture name must be provided).
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
config_path: Path to a ``trustbench.yaml`` config file.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Configured :class:`TrustBench` instance ready to call :meth:`evaluate`.
|
|
156
|
+
"""
|
|
157
|
+
from trustbench.config.loader import load_config
|
|
158
|
+
from trustbench.models.checkpoint import load_model_from_checkpoint
|
|
159
|
+
from trustbench.models.pytorch import PyTorchModelAdapter
|
|
160
|
+
|
|
161
|
+
cfg = load_config(config_path)
|
|
162
|
+
mcfg = cfg.model
|
|
163
|
+
dcfg = cfg.dataset
|
|
164
|
+
rcfg = cfg.reporting
|
|
165
|
+
|
|
166
|
+
# Model
|
|
167
|
+
if mcfg.checkpoint_path is None:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"config.model.checkpoint_path is required for TrustBench.from_config()."
|
|
170
|
+
)
|
|
171
|
+
if mcfg.arch_name is None:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"config.model.arch_name is required for TrustBench.from_config()."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
ckpt_path = cfg.resolve_path(mcfg.checkpoint_path)
|
|
177
|
+
nn_module, _ = load_model_from_checkpoint(
|
|
178
|
+
ckpt_path, model_name=mcfg.arch_name, device=mcfg.device
|
|
179
|
+
)
|
|
180
|
+
model_adapter = PyTorchModelAdapter(
|
|
181
|
+
nn_module, num_classes=mcfg.num_classes, device=mcfg.device
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Dataset
|
|
185
|
+
dataset_adapter = cls._build_dataset_from_config(cfg)
|
|
186
|
+
|
|
187
|
+
output_dir: Path | None = Path(
|
|
188
|
+
cfg.resolve_path(rcfg.output_dir)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
class_names = None
|
|
192
|
+
if dcfg.dataset_type.lower() == "cifar10":
|
|
193
|
+
from trustbench.datasets.cifar10 import CIFAR10_CLASSES
|
|
194
|
+
|
|
195
|
+
class_names = list(CIFAR10_CLASSES)
|
|
196
|
+
|
|
197
|
+
return cls(
|
|
198
|
+
model_adapter,
|
|
199
|
+
dataset_adapter,
|
|
200
|
+
metrics=cfg.metrics.groups,
|
|
201
|
+
batch_size=dcfg.batch_size,
|
|
202
|
+
seed=cfg.seed,
|
|
203
|
+
output_dir=output_dir,
|
|
204
|
+
class_names=class_names,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def _build_dataset_from_config(cfg: Any) -> DatasetAdapter:
|
|
209
|
+
"""Instantiate the correct DatasetAdapter from config."""
|
|
210
|
+
dcfg = cfg.dataset
|
|
211
|
+
dtype = dcfg.dataset_type.lower()
|
|
212
|
+
|
|
213
|
+
if dtype == "cifar10":
|
|
214
|
+
from trustbench.datasets.cifar10 import (
|
|
215
|
+
CIFAR10DatasetAdapter,
|
|
216
|
+
get_cifar10_datasets,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
data_dir = (
|
|
220
|
+
str(cfg.resolve_path(dcfg.data_dir))
|
|
221
|
+
if dcfg.data_dir
|
|
222
|
+
else "./data"
|
|
223
|
+
)
|
|
224
|
+
trainset, testset = get_cifar10_datasets(data_dir=data_dir, download=True)
|
|
225
|
+
split_dataset = trainset if dcfg.split == "train" else testset
|
|
226
|
+
return CIFAR10DatasetAdapter(split_dataset)
|
|
227
|
+
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"Unknown dataset_type {dcfg.dataset_type!r}. "
|
|
230
|
+
"Supported types: 'cifar10'."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# Evaluation
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def evaluate(self) -> EvaluationResult:
|
|
238
|
+
"""Run the full evaluation pipeline and return an :class:`EvaluationResult`.
|
|
239
|
+
|
|
240
|
+
If *output_dir* was provided at construction, artifacts are automatically
|
|
241
|
+
saved via :func:`~trustbench.reporting.save_result`.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
:class:`EvaluationResult` containing metrics, predictions, and metadata.
|
|
245
|
+
"""
|
|
246
|
+
result = _core_evaluate(
|
|
247
|
+
self._model_adapter,
|
|
248
|
+
self._dataset_adapter,
|
|
249
|
+
metrics=self._metrics,
|
|
250
|
+
batch_size=self._batch_size,
|
|
251
|
+
class_names=self._class_names,
|
|
252
|
+
seed=self._seed,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if self._output_dir is not None:
|
|
256
|
+
from trustbench.reporting.result_io import save_result
|
|
257
|
+
|
|
258
|
+
save_result(result, self._output_dir)
|
|
259
|
+
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
def run(self) -> EvaluationResult:
|
|
263
|
+
"""Alias for :meth:`evaluate`."""
|
|
264
|
+
return self.evaluate()
|