ShortcutDetector API¶
ShortcutDetector runs any of the 19 registered methods on one embedding matrix, compares each against its fixed threshold and aggregates the flags into a risk label. It is the interactive pipeline behind the dashboard and the reports; the paper's measurement is conditional_audit.
Class reference¶
ShortcutDetector
¶
ShortcutDetector(
methods: list[str] = None,
seed: int = 42,
condition_name: str = "indicator_count",
condition_kwargs: dict[str, Any] | None = None,
**kwargs: Any
)
Unified interface for detecting shortcuts using multiple methods.
Combines five detection approaches: 1. HBAC (Hierarchical Bias-Aware Clustering) 2. Probe-based detection (train classifiers on embeddings) 3. Statistical testing (feature-wise group differences) 4. Geometric shortcut analysis (subspace monitoring) 5. Equalized odds gap analysis (fairness-based)
Example
import numpy as np rng = np.random.default_rng(0) embeddings, labels, groups = rng.normal(size=(200, 16)), rng.integers(0, 2, 200), rng.integers(0, 2, 200) detector = ShortcutDetector( ... methods=['hbac', 'probe', 'statistical', 'geometric', 'equalized_odds'] ... ) results = detector.fit(embeddings, labels, group_labels=groups) # doctest: +SKIP print(detector.summary()) # doctest: +SKIP
Initialize unified shortcut detector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
methods
|
list[str]
|
List of methods to use. Options: 'hbac', 'probe', 'statistical' |
None
|
seed
|
int
|
Random seed for reproducibility |
42
|
condition_name
|
str
|
Registered overall assessment condition name |
'indicator_count'
|
condition_kwargs
|
dict[str, Any] | None
|
Keyword arguments passed to the selected condition |
None
|
**kwargs
|
Any
|
Additional arguments passed to individual detectors |
{}
|
Source code in shortcut_detect/unified.py
Methods:¶
fit
¶
fit(
embeddings: ndarray | None,
labels: ndarray,
group_labels: ndarray | None = None,
feature_names: list[str] | None = None,
raw_inputs: Sequence[Any] | None = None,
embedding_source: EmbeddingSource | None = None,
embedding_cache_path: str | None = None,
force_embedding_recompute: bool = False,
splits: dict[str, ndarray] | None = None,
extra_labels: dict[str, ndarray] | None = None,
) -> ShortcutDetector
Fit all detection methods on embeddings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ndarray | None
|
(n_samples, embedding_dim) array. Any width and any modality (image, text, audio, tabular, multimodal encoders): the detectors only see the float matrix. Optional when using embedding-only mode. |
required |
labels
|
ndarray
|
(n_samples,) target labels |
required |
group_labels
|
ndarray | None
|
(n_samples,) group labels (e.g., demographic attributes)
If None, uses |
None
|
feature_names
|
list[str] | None
|
Optional list of feature names |
None
|
raw_inputs
|
Sequence[Any] | None
|
Optional raw inputs (text, ids, etc.). Required when
|
None
|
embedding_source
|
EmbeddingSource | None
|
Source used to generate embeddings when they are not pre-computed. |
None
|
embedding_cache_path
|
str | None
|
Optional path to cache generated embeddings. |
None
|
force_embedding_recompute
|
bool
|
Whether to ignore cached embeddings. |
False
|
splits
|
dict[str, ndarray] | None
|
Optional dictionary of named index sets for semi-supervised methods. Expected keys: 'train_l' (labeled), 'train_u' (unlabeled). |
None
|
extra_labels
|
dict[str, ndarray] | None
|
Optional dictionary of named per-sample arrays for additional supervision signals (e.g., 'spurious' labels). Use -1 for unknown labels. |
None
|
Returns:
| Type | Description |
|---|---|
ShortcutDetector
|
self |
Source code in shortcut_detect/unified.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | |
summary
¶
Generate a text summary of detection results.
Returns:
| Type | Description |
|---|---|
str
|
Formatted summary string |
Source code in shortcut_detect/unified.py
generate_report
¶
generate_report(
output_path: str = None,
format: str = "html",
include_visualizations: bool = True,
export_csv: bool = False,
csv_dir: str = None,
)
Generate comprehensive report with visualizations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
Path to save report (required for HTML/PDF) |
None
|
format
|
str
|
Report format ('html' or 'pdf') |
'html'
|
include_visualizations
|
bool
|
Whether to include plots in HTML/PDF |
True
|
export_csv
|
bool
|
Whether to export results to CSV files |
False
|
csv_dir
|
str
|
Directory to save CSV files (default: same dir as output_path) |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If format is not supported or required paths are missing |
Source code in shortcut_detect/unified.py
get_results
¶
Get raw results dictionary.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with results from all methods |
Quick reference¶
Constructor¶
ShortcutDetector(
methods: list[str] = ['hbac', 'probe', 'statistical'],
seed: int = 42,
condition_name: str = 'indicator_count',
condition_kwargs: dict | None = None,
**kwargs
)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
methods |
list[str] | ['hbac', 'probe', 'statistical'] |
Methods to run |
seed |
int | 42 |
Random seed for reproducibility |
condition_name |
str | 'indicator_count' |
Overall assessment condition to use |
condition_kwargs |
dict | None |
Keyword args passed to the selected condition |
**kwargs |
dict | - | Settings read by the method builders (see Custom parameters) |
Available methods¶
shortcut_detect.discovery.available_method_plugins() returns the 19 keys:
| Method key | Description |
|---|---|
'hbac' |
Hierarchical bias-aware clustering |
'probe' |
Probe classifier (probe_backend="sklearn" or "torch") |
'statistical' |
Feature-wise two-sample tests with BH correction |
'geometric' |
Centroid distance and subspace overlap |
'bias_direction_pca' |
Projection gap along the bias direction |
'frequency' |
Signal concentrated in few dimensions |
'equalized_odds', 'demographic_parity', 'intersectional' |
Fairness gaps of a classifier trained on the embeddings |
'groupdro', 'gce', 'ssa', 'early_epoch_clustering' |
Training-dynamics methods |
'causal_effect', 'generative_cvae', 'vae' |
Causal and generative methods |
'sis' |
Sufficient input subsets |
'cav', 'gradcam_mask_overlap' |
Loader mode: fit_from_loaders with concept sets or heatmaps |
Methods¶
fit()¶
def fit(
embeddings: np.ndarray | None,
labels: np.ndarray,
group_labels: np.ndarray | None = None,
feature_names: list[str] | None = None,
raw_inputs: Sequence | None = None,
embedding_source: EmbeddingSource | None = None,
embedding_cache_path: str | None = None,
force_embedding_recompute: bool = False,
splits: dict[str, np.ndarray] | None = None,
extra_labels: dict[str, np.ndarray] | None = None,
) -> ShortcutDetector
Fits every selected method.
| Parameter | Type | Description |
|---|---|---|
embeddings |
ndarray or None | Shape (n_samples, embedding_dim); None for embedding-only mode |
labels |
ndarray | Task labels, shape (n_samples,) |
group_labels |
ndarray or None | Protected attribute, shape (n_samples,). When None, labels is used for the group-based methods |
feature_names |
list | Optional names for the dimensions |
raw_inputs |
sequence | Raw inputs (texts, ids) for embedding_source |
embedding_source |
EmbeddingSource | Generates embeddings when embeddings is None |
embedding_cache_path |
str | Cache file for generated embeddings |
force_embedding_recompute |
bool | Ignore the cache |
splits |
dict | train_l and train_u index sets for ssa |
extra_labels |
dict | Extra per-sample arrays (for example several attributes for intersectional and causal_effect); -1 marks unknown |
Returns: self
summary()¶
One block per method (risk label, reason, key numbers) followed by the overall assessment.
generate_report()¶
def generate_report(
output_path: str = None,
format: str = 'html',
include_visualizations: bool = True,
export_csv: bool = False,
csv_dir: str = None,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
output_path |
str | required | Path for the report |
format |
str | 'html' |
'html', 'pdf' or 'markdown' |
include_visualizations |
bool | True | Embed plots |
export_csv |
bool | False | Also write the CSV tables |
csv_dir |
str | None | Where the CSVs go (default: csv_results/ next to the report) |
get_results()¶
{method: result dict}; see Accessing raw results.
fit_from_loaders()¶
Loader mode for cav and gradcam_mask_overlap; see Loader-based CAV usage.
Attributes (after fit)¶
| Attribute | Type | Description |
|---|---|---|
results_ |
dict | Results from all detection methods |
detectors_ |
dict | Fitted detector instance per method |
embeddings_ |
ndarray | Embeddings used for fitting |
labels_ |
ndarray | Task labels used for fitting |
group_labels_ |
ndarray | Group labels (labels when none were given) |
protected_labels_ |
ndarray or None | The group_labels as passed |
Usage examples¶
Basic usage¶
import numpy as np
from shortcut_detect import ShortcutDetector
embeddings = np.load("embeddings.npy")
task_labels = np.load("task.npy")
group_labels = np.load("groups.npy")
detector = ShortcutDetector(methods=['hbac', 'probe', 'statistical', 'geometric'], seed=42)
detector.fit(embeddings, task_labels, group_labels=group_labels)
print(detector.summary())
results = detector.get_results()
print(f"Probe score: {results['probe']['results']['metrics']['metric_value']:.2f} ({results['probe']['risk_value']})")
Custom parameters¶
Keyword arguments reach the method builders; each reads the ones it knows:
detector = ShortcutDetector(
methods=['hbac', 'probe', 'statistical'],
seed=42,
probe_backend='sklearn', # or 'torch'
hbac_target='attribute', # or 'task'
)
Other builder settings include dp_gap_threshold, eo_tpr_gap_threshold, eo_fpr_gap_threshold, freq_target, device and epochs; see each builder in shortcut_detect/<method>/builder.py.
Custom overall assessment¶
The condition_name parameter selects how method-level results are aggregated into
the final risk summary. Five built-in conditions are available:
| Condition | Description | Key parameters |
|---|---|---|
indicator_count |
Counts total risk indicators across methods (default) | - |
majority_vote |
Counts methods with at least one indicator as votes | high_threshold (int) |
weighted_risk |
Weights each detector by evidence strength (probe score, stat significance ratio, HBAC confidence, geometric effect size) | high_threshold (float), moderate_threshold (float) |
multi_attribute |
Cross-references risk across sensitive attributes; escalates when several attributes flag shortcuts on their own | high_threshold (int) |
meta_classifier |
Trained sklearn meta-classifier on detector features, or heuristic fallback | model_path (str), high_threshold (float), moderate_threshold (float) |
# Majority vote
detector = ShortcutDetector(
methods=['probe', 'statistical'],
condition_name='majority_vote',
condition_kwargs={'high_threshold': 2},
)
# Weighted risk scoring
detector = ShortcutDetector(
methods=['hbac', 'probe', 'statistical', 'geometric'],
condition_name='weighted_risk',
condition_kwargs={'high_threshold': 0.6, 'moderate_threshold': 0.3},
)
# Multi-attribute intersection
detector = ShortcutDetector(
methods=['probe', 'statistical'],
condition_name='multi_attribute',
condition_kwargs={'high_threshold': 2},
)
# Meta-classifier (heuristic fallback when no trained model provided)
detector = ShortcutDetector(
methods=['probe', 'statistical', 'hbac'],
condition_name='meta_classifier',
)
# Meta-classifier with a trained model
detector = ShortcutDetector(
methods=['probe', 'statistical', 'hbac'],
condition_name='meta_classifier',
condition_kwargs={'model_path': 'path/to/meta_model.joblib'},
)
The meta_classifier condition also exposes MetaClassifierCondition.extract_features(ctx)
for building training datasets from synthetic benchmark runs.
The default indicator_count condition preserves the library's existing summary semantics.
Embedding-only mode¶
from shortcut_detect import ShortcutDetector, HuggingFaceEmbeddingSource
hf_source = HuggingFaceEmbeddingSource(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
detector = ShortcutDetector(methods=['probe', 'statistical'])
detector.fit(
embeddings=None, # triggers embedding-only mode
labels=task_labels,
group_labels=groups,
raw_inputs=texts,
embedding_source=hf_source,
embedding_cache_path="embeddings.npy",
)
Report generation¶
detector.generate_report(output_path="report.html", format="html", include_visualizations=True)
detector.generate_report(output_path="report.pdf", format="pdf") # needs WeasyPrint's system libraries
detector.generate_report(output_path="report.md", format="markdown")
detector.generate_report(output_path="report.html", format="html", export_csv=True, csv_dir="csv_results")
Accessing raw results¶
results = detector.get_results()
hbac = results['hbac']
print(hbac['risk_value'], hbac['report']['has_shortcut'])
probe = results['probe']
print(probe['risk_value'], probe['results']['metrics'])
statistical = results['statistical']
print(statistical['risk_value'], {k: len(v or []) for k, v in statistical['significant_features'].items()})
geometric = results['geometric']
print(geometric['risk_value'], geometric['summary'], geometric['bias_pairs'][0].effect_size)
Every entry carries detector (the fitted instance), summary_title, summary_lines, risk_indicators, success, risk_value, risk_label and risk_reason; the method-specific keys above sit next to them. A method that failed has success=False and an error.
Loader-based CAV usage¶
detector = ShortcutDetector(methods=["cav"])
detector.fit_from_loaders({
"cav": {
"concept_sets": {"shortcut": concept_arr},
"random_set": random_arr,
"target_directional_derivatives": directional_derivatives, # optional but needed for TCAV risk
}
})
print(detector.get_results()["cav"]["metrics"])
Error handling¶
try:
detector.fit(embeddings, task_labels, group_labels=group_labels)
except ValueError as e:
print(f"Invalid input: {e}")
except RuntimeError as e:
print(f"Detection failed: {e}")