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

loader_name

None

Logical split name (for example train_loader).

batch_size / shuffle / num_workers

1 / False / 0

Loader runtime behavior.

is_training

False

Enables deny-aware sampling logic for train split usage.

compute_hash

True

Stable sample identifiers across runs.

preload_labels / preload_metadata

True / True

Startup latency vs. runtime access trade-off.

array_return_proxies / array_use_cache

True / True

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=True turns on deny-aware sampling: a discarded id silently disappears from the batches, with no change to the loop.

  • loader.wrapped_dataset is the tracking wrapper (loader.dataset stays the raw dataset you passed in) and wrapped_dataset.unique_ids are the ids WeightsLab assigned.

  • batch_size given 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_loaders

  • list_uids [loader] [--discarded] [--limit N] — real sample ids, tags and discard state, read from the tracked sample dataframe

  • discard <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