Data Exploration¶
Data exploration focuses on tracking sample IDs, tagging, discarding, filtering, and exporting subsets while training.
Data wrapper parameters (flag="data")¶
Key wl.watch_or_edit(dataset, flag="data", ...) parameters:
Parameter |
Default |
Behavior |
|---|---|---|
|
|
Logical split name (for example |
|
|
Loader runtime behavior. |
|
|
Enables deny-aware sampling logic for train split usage. |
|
|
Stable sample identifiers across runs. |
|
|
Startup latency vs. runtime access trade-off. |
|
|
Lazy array retrieval and cache behavior for large data. |
For complete API details, see User Functions Reference and Configuration.
Core SDK calls¶
Typical data exploration functions:
wl.tag_samples(sample_ids, tag, mode="add"|"remove")wl.discard_samples(sample_ids, discarded=True|False)wl.get_samples_by_tag(tag, origin=...)wl.get_discarded_samples(origin=...)wl.write_dataframe(path, format="json"|"csv", ...)
origin is the loader_name you registered ("train_loader",
"val_loader", …); None searches every split.
Example:
import weightslab as wl
wl.tag_samples([10, 42, 77], "hard_examples", mode="add")
wl.discard_samples([5, 9], discarded=True)
hard_ids = wl.get_samples_by_tag("hard_examples", origin="train_loader")
discarded_ids = wl.get_discarded_samples(origin="train_loader")
wl.write_dataframe(
path="artifacts/hard_examples.csv",
format="csv",
sample_id=hard_ids,
columns=["signals", "discarded", "tag:hard_examples"],
)
Standalone data-only integration (UI + CLI ready)¶
A complete, runnable MNIST curation script with no model at all: it wraps the two datasets, walks them once, tags a digit class, discards a slice, queries both back, checks that discarded ids really leave the training batches, and exports the result to CSV.
Bundled example: weightslab/examples/PyTorch/wl-standalone-data/main.py
weightslab start example --data # run it (MNIST downloads on first run)
weightslab cli # attach a terminal, in another shell
weightslab start # open Weights Studio, in a third shell
def main(argv=None) -> int:
args = parse_args(argv)
# The data level needs an experiment directory (h5 sample store, exports) but
# NOT the config level: WEIGHTSLAB_ROOT_LOG_DIR is the documented way to give
# an otherwise unconfigured run one.
log_dir = resolve_log_dir(args.log_dir, "standalone_data")
os.environ["WEIGHTSLAB_ROOT_LOG_DIR"] = str(log_dir)
import weightslab as wl
# --- the only WeightsLab registrations in this file -----------------------
train_loader = wl.watch_or_edit(
MnistSlice(args.data_root, train=True, max_samples=args.max_samples),
flag="data",
loader_name="train_loader",
batch_size=args.batch_size,
shuffle=True,
is_training=True, # deny-aware sampling: discarded ids leave the batches
compute_hash=False, # True = content hashes, stable across runs but slower
preload_labels=True,
preload_metadata=False,
root_log_dir=str(log_dir),
)
val_loader = wl.watch_or_edit(
MnistSlice(args.data_root, train=False, max_samples=args.max_samples),
flag="data",
loader_name="val_loader",
batch_size=args.batch_size,
shuffle=False,
is_training=False,
compute_hash=False,
preload_labels=True,
preload_metadata=False,
root_log_dir=str(log_dir),
)
# --------------------------------------------------------------------------
wl.serve(serving_grpc=not args.no_grpc, serving_cli=not args.no_cli,
grpc_port=args.grpc_port)
print("=" * 70)
print(" DATA-ONLY standalone — attach with `weightslab cli`, UI with `weightslab start`")
print(f" train={len(train_loader.dataset)} val={len(val_loader.dataset)} log_dir={log_dir}")
print("=" * 70)
wl.start_training(timeout=3)
# 1) Curation pass: the tracked loader yields (inputs, ids, targets).
to_tag: list = []
seen = 0
for _ in range(args.epochs):
for inputs, ids, targets in train_loader:
seen += len(ids)
hits = (targets.view(-1) == args.tag_digit).nonzero().view(-1).tolist()
to_tag += [int(ids[i]) for i in hits]
# 2) Tag / discard through the SDK (identical to what the UI grid does).
wl.tag_samples(to_tag, args.tag_name, mode="add")
# `wrapped_dataset` is the tracking wrapper (loader.dataset is the raw one);
# `unique_ids` are the stable ids WeightsLab assigned to every sample.
tracked = train_loader.wrapped_dataset
first_ids = [int(i) for i in tracked.unique_ids[: args.discard_first]]
wl.discard_samples(first_ids, discarded=True)
# 3) Query the curation state back.
tagged = wl.get_samples_by_tag(args.tag_name, origin="train_loader")
discarded = wl.get_discarded_samples(origin="train_loader")
print(f"[data-level] visited {seen} samples")
print(f"[data-level] tagged '{args.tag_name}': {len(tagged)} -> {tagged[:10]}")
print(f"[data-level] discarded: {len(discarded)} -> {discarded[:10]}")
# 4) A discarded id no longer shows up in training batches (is_training=True).
remaining = set()
for _, ids, _ in train_loader:
remaining.update(int(i) for i in ids)
leaked = remaining & set(discarded)
print(f"[data-level] discarded ids still sampled: {len(leaked)} (expected 0)")
# 5) Export the curated subset.
export = log_dir / "curated_samples.csv"
written = wl.write_dataframe(
path=str(export),
format="csv",
columns=["discarded", f"tag:{args.tag_name}"],
)
print(f"[data-level] exported {written}")
wl.keep_serving(timeout=args.serve_timeout)
return 0
Notes on the wrapped loaders:
A plain
(image, label)dataset is enough. The wrapper injects the stable sample id, so the tracked loader yields(images, ids, targets).is_training=Trueturns on deny-aware sampling: a discarded id silently disappears from the batches, with no change to the loop.loader.wrapped_datasetis the tracking wrapper (loader.datasetstays the raw dataset you passed in) andwrapped_dataset.unique_idsare the ids WeightsLab assigned.batch_sizegiven here is authoritative; the hyperparameters config can override it later (data.<loader_name>.batch_size) but never has to exist.
CLI and UI surfaces¶
CLI:
list_loaderslist_uids [loader] [--discarded] [--limit N]— real sample ids, tags and discard state, read from the tracked sample dataframediscard <uid...>/undiscard <uid...>add_tag <sample_id> <tag> ...dump/ledger_dump
UI:
filter/sort the grid
add/remove tags
discard/restore rows
inspect samples and metadata