Probes API¶
The probes module trains a classifier to predict the group from the embeddings. Both probes subclass DetectorBase and do their own train/test split or cross-validation inside fit.
Class reference¶
SKLearnProbe¶
SKLearnProbe
¶
SKLearnProbe(
estimator: BaseEstimator | None = None,
*,
metric: MetricName = "f1",
threshold: float = 0.7,
average: str = "macro",
evaluation: EvaluationName = "holdout",
test_size: float = 0.2,
cv_folds: int = 5,
random_state: int = 0
)
Bases: DetectorBase
Shortcut detector based on training a classifier to predict a demographic target.
Idea
- Train a probe classifier to predict a sensitive/demographic attribute y from embeddings X.
- If the probe performs above a user-defined threshold on a metric (e.g., F1), treat this as evidence that embeddings encode the attribute (potential shortcut).
Parameters¶
estimator: Any scikit-learn estimator supporting fit/predict (optionally predict_proba or decision_function). If None, uses a standardized LogisticRegression. metric: One of: "accuracy", "f1", "precision", "recall", "roc_auc". threshold: Shortcut is detected if metric_value > threshold. average: Averaging strategy for multiclass f1/precision/recall ("macro", "micro", "weighted"). For binary problems, "binary" is used automatically for these metrics. evaluation: "holdout" (train/test split), "cv" (StratifiedKFold cross-validation), or "train" (no splitting). test_size: Used for holdout split. cv_folds: Used for CV. random_state: Reproducibility for splitting.
Fit inputs¶
embeddings: np.ndarray, shape (n_samples, n_features) target: np.ndarray, shape (n_samples,) Demographic/sensitive attribute labels (e.g., gender).
Source code in shortcut_detect/probes/sklearn_probe.py
TorchProbe¶
TorchProbe
¶
TorchProbe(
model: Module,
loss_fn: Any,
*,
optimizer_class: Any = torch.optim.Adam,
optimizer_kwargs: dict[str, Any] | None = None,
device: str | None = None,
metric: MetricName = "accuracy",
threshold: float = 0.7,
test_size: float = 0.2,
random_state: int = 0,
epochs: int = 10,
batch_size: int = 128,
num_workers: int = 0,
early_stopping: int | None = None,
use_amp: bool = False,
verbose: bool = False,
loader_factory: LoaderFactory | None = None,
stage_loader_overrides: StageLoaderOverrides = None
)
Bases: DetectorBase
Probe-based shortcut detector using a PyTorch model.
Fits a torch model to predict a demographic target from embeddings and flags a shortcut if the chosen metric exceeds a threshold.
Parameters¶
model: torch.nn.Module that maps embeddings -> logits (classification) or scalar (regression). For classification, return shape (N, C) logits. loss_fn: Loss function (e.g., nn.CrossEntropyLoss()). optimizer_class / optimizer_kwargs: Optimizer configuration. device: "cpu" or "cuda"; defaults to CUDA if available. metric: One of: "accuracy", "f1", "roc_auc", "loss". For multiclass, "f1" uses macro averaging; "roc_auc" only supported for binary. threshold: shortcut_detected is True when metric_value > threshold (except for "loss", where shortcut_detected is True when loss < threshold if you choose to use loss; see notes).
Source code in shortcut_detect/probes/torch_probe.py
Methods:¶
fit_dataset
¶
fit_dataset(
dataset: Dataset,
*,
val_dataset: Dataset | None = None,
target_extractor: Callable[[Any], Any] | None = None,
data_spec: DataSpec | dict[str, Any] | None = None
) -> TorchProbe
Train using map-style or iterable datasets without materializing full arrays.
Source code in shortcut_detect/probes/torch_probe.py
fit_loaders
¶
fit_loaders(
train_loader: DataLoader,
*,
val_loader: DataLoader,
target_extractor: Callable[[Any], Any] | None = None,
data_spec: DataSpec | dict[str, Any] | None = None
) -> TorchProbe
Train/evaluate from user-provided loaders.
Source code in shortcut_detect/probes/torch_probe.py
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 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | |
predict
¶
Predict class labels for embeddings (requires prior fit).
Source code in shortcut_detect/probes/torch_probe.py
SKLearnProbe¶
Constructor¶
SKLearnProbe(
estimator: BaseEstimator | None = None,
*,
metric: str = "f1",
threshold: float = 0.70,
average: str = "macro",
evaluation: str = "holdout",
test_size: float = 0.2,
cv_folds: int = 5,
random_state: int = 0,
)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
estimator |
sklearn estimator | standardised LogisticRegression |
Any estimator with fit and predict |
metric |
str | "f1" |
"accuracy", "f1", "precision", "recall" or "roc_auc" |
threshold |
float | 0.70 | Shortcut when the metric exceeds it |
average |
str | "macro" |
Averaging for multiclass f1, precision, recall |
evaluation |
str | "holdout" |
"holdout", "cv" or "train" |
test_size |
float | 0.2 | Held-out share for "holdout" |
cv_folds |
int | 5 | Folds for "cv" |
random_state |
int | 0 | Seed for the split |
Methods¶
fit()¶
Splits (or cross-validates), trains the estimator, scores it and fills results_.
predict()¶
Labels from the fitted estimator.
get_report(), summary()¶
The standard detector schema and its one-line summary.
Attributes¶
| Attribute | Type | Description |
|---|---|---|
metric_value_ |
float | Held-out or cross-validated score |
shortcut_detected_ |
bool | metric_value_ > threshold |
estimator_ |
estimator | Fitted estimator |
y_true_eval_, y_pred_eval_ |
ndarray | Held-out labels and predictions (holdout and train evaluation) |
results_ |
dict | Standard schema; results_["report"] holds fold_scores, mean_score, std_score under evaluation="cv" |
Usage¶
from sklearn.linear_model import LogisticRegression
from shortcut_detect import SKLearnProbe
probe = SKLearnProbe(LogisticRegression(max_iter=1000), metric="accuracy",
evaluation="holdout", test_size=0.2, random_state=42)
probe.fit(embeddings, group_labels)
print(f"Accuracy: {probe.metric_value_:.2%}, flagged: {probe.shortcut_detected_}")
TorchProbe¶
Constructor¶
TorchProbe(
model: nn.Module,
loss_fn,
*,
optimizer_class=torch.optim.Adam,
optimizer_kwargs: dict | None = None, # {"lr": 1e-3}
device: str | None = None,
metric: str = "accuracy",
threshold: float = 0.70,
test_size: float = 0.2,
random_state: int = 0,
epochs: int = 10,
batch_size: int = 128,
num_workers: int = 0,
early_stopping: int | None = None,
use_amp: bool = False,
verbose: bool = False,
loader_factory=None,
stage_loader_overrides=None,
)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
model |
nn.Module | required | The probe network |
loss_fn |
callable | required | For example nn.CrossEntropyLoss() |
optimizer_class |
class | torch.optim.Adam |
Optimizer |
optimizer_kwargs |
dict | {"lr": 1e-3} |
Optimizer arguments |
device |
str or None | None | "cuda" when available, else "cpu" |
metric |
str | "accuracy" |
Score reported in metric_value_ |
threshold |
float | 0.70 | Shortcut when the metric exceeds it |
test_size |
float | 0.2 | Held-out share |
epochs |
int | 10 | Training epochs |
batch_size |
int | 128 | Batch size |
early_stopping |
int or None | None | Patience in epochs |
loader_factory |
callable or None | None | Builds a DataLoader per stage |
stage_loader_overrides |
dict or None | None | Per-stage DataLoader kwargs |
Methods¶
fit(embeddings, target): in-memory arraysfit_dataset(dataset, ...): a map-style or iterabletorch.utils.data.Datasetyielding{"embeddings": x, "labels": y}fit_loaders(...): pre-built DataLoaders; see the class referencepredict(X): labels from the trained network
Attributes¶
| Attribute | Type | Description |
|---|---|---|
metric_value_ |
float | Held-out score |
shortcut_detected_ |
bool | metric_value_ > threshold |
n_features_in_ |
int | Input width seen in fit |
results_ |
dict | Standard schema |
Usage¶
import torch.nn as nn
from shortcut_detect import TorchProbe
class CustomProbe(nn.Module):
def __init__(self, input_dim, n_classes):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(128, n_classes),
)
def forward(self, x):
return self.net(x)
probe = TorchProbe(
model=CustomProbe(512, 3),
loss_fn=nn.CrossEntropyLoss(),
device="cuda",
epochs=50,
)
probe.fit(embeddings, group_labels)
print(probe.metric_value_, probe.shortcut_detected_)