Buckets:
| """ | |
| Data loading, splitting, filtering, and result-printing utilities. | |
| """ | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| from datasets import load_dataset | |
| def collect_text_values(value): | |
| """Recursively extract all non-empty strings from str/list/dict values. | |
| Handles nested structures because some HF datasets store messages as lists of dicts | |
| (e.g. ShareGPT conversation format) rather than flat strings. | |
| """ | |
| if isinstance(value, str): | |
| text = " ".join(value.split()) | |
| return [text] if text else [] | |
| if isinstance(value, list): | |
| out = [] | |
| for item in value: | |
| out.extend(collect_text_values(item)) | |
| return out | |
| if isinstance(value, dict): | |
| out = [] | |
| for item in value.values(): | |
| out.extend(collect_text_values(item)) | |
| return out | |
| return [] | |
| def extract_row_text(ex, spec): | |
| """Concatenate text from the spec's target columns, falling back to all columns.""" | |
| parts = [] | |
| for column in spec["columns"]: | |
| if column in ex: | |
| parts.extend(collect_text_values(ex[column])) | |
| # fallback: scan every column if the target columns yielded nothing | |
| if not parts: | |
| for value in ex.values(): | |
| parts.extend(collect_text_values(value)) | |
| return " ".join(" ".join(parts).split()) | |
| def load_topic_texts(topic, spec, seed, needed): | |
| """Stream `needed` rows from a HuggingFace dataset and extract text.""" | |
| print(f" loading {topic}: {spec['label']} ...") | |
| kwargs = {"path": spec["hf_id"], "split": spec["split"], "streaming": True} | |
| if spec["config"]: | |
| kwargs["name"] = spec["config"] | |
| try: | |
| ds = load_dataset(**kwargs) | |
| except RuntimeError as exc: | |
| if "Dataset scripts are no longer supported" in str(exc): | |
| raise RuntimeError( | |
| f"{topic} dataset {spec['hf_id']} is script-bound. " | |
| "Use a parquet/json/csv-backed Hub dataset instead." | |
| ) from exc | |
| raise | |
| # streaming shuffle only randomizes within the buffer window, not the full dataset | |
| ds = ds.shuffle(seed=seed, buffer_size=10_000) | |
| texts = [] | |
| for ex in ds: | |
| text = extract_row_text(ex, spec) | |
| if text: | |
| texts.append(text) | |
| if len(texts) >= needed: | |
| break | |
| print(f" {len(texts)} rows from {spec['columns']}") | |
| if len(texts) < needed: | |
| print(f" warning: requested {needed}, found {len(texts)} usable rows") | |
| return texts | |
| def load_topic_data(datasets_cfg, n_pos, seed, jsonl_path=None): | |
| """Load topic texts either from a JSONL file or from HuggingFace datasets. | |
| If jsonl_path is provided and exists, reads prompts from the JSONL; otherwise | |
| streams each dataset defined in datasets_cfg from HuggingFace. | |
| """ | |
| if jsonl_path is not None and Path(jsonl_path).exists(): | |
| data: dict[str, list[str]] = {} | |
| n_skipped = 0 | |
| with Path(jsonl_path).open(encoding="utf-8") as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| record = json.loads(line) | |
| # only use clean generations; failures have prompt=None | |
| if record.get("_status") != "ok": | |
| n_skipped += 1 | |
| continue | |
| topic = record["topic"] | |
| prompt = record["prompt"] | |
| if topic not in data: | |
| data[topic] = [] | |
| if len(data[topic]) < n_pos: | |
| data[topic].append(prompt) | |
| if n_skipped: | |
| print(f" skipped {n_skipped} non-ok records") | |
| if not data: | |
| raise ValueError(f"no usable records found in {jsonl_path}") | |
| else: | |
| data = {} | |
| for topic, spec in datasets_cfg.items(): | |
| texts = load_topic_texts(topic, spec, seed, n_pos) | |
| data[topic] = texts[:n_pos] | |
| for topic, texts in data.items(): | |
| print(f" {topic}: {len(texts)} samples") | |
| return data | |
| def split_data(data, ratio, seed=42): | |
| """Shuffle then split each topic's list into train/test by ratio.""" | |
| rng = np.random.default_rng(seed) | |
| train, test = {}, {} | |
| for topic, texts in data.items(): | |
| shuffled = texts.copy() | |
| rng.shuffle(shuffled) | |
| split = int(len(shuffled) * ratio) | |
| train[topic] = shuffled[:split] | |
| test[topic] = shuffled[split:] | |
| return train, test | |
| def build_labeled_set(data, topics): | |
| """Flatten topic→texts dict into (texts, labels) arrays, topics indexed by position.""" | |
| texts, labels = [], [] | |
| for i, topic in enumerate(topics): | |
| texts.extend(data[topic]) | |
| labels.extend([i] * len(data[topic])) | |
| return texts, np.array(labels) | |
| def filter_dead_samples(hidden_states, labels, texts, generations, layers, split_name): | |
| """Drop samples whose first generated token was EOS (zero hidden-state steps).""" | |
| # all layers share the same dead/alive mask, so checking one layer is sufficient | |
| ref_layer = layers[0] | |
| alive_mask = np.array([hs.shape[0] > 0 for hs in hidden_states[ref_layer]]) | |
| n_dead = int((~alive_mask).sum()) | |
| n_total = len(alive_mask) | |
| print(f" {split_name}: {n_dead}/{n_total} dead samples (first token was EOS)") | |
| if n_dead == 0: | |
| return hidden_states, labels, texts, generations, n_dead | |
| alive_idx = np.where(alive_mask)[0] | |
| filtered = {layer: [hidden_states[layer][i] for i in alive_idx] for layer in layers} | |
| filtered_texts = [texts[i] for i in alive_idx] | |
| filtered_gens = [generations[i] for i in alive_idx] | |
| return filtered, labels[alive_idx], filtered_texts, filtered_gens, n_dead | |
| def build_dataset_df(texts, gens, labels, topics, hidden_states_for_ref_layer): | |
| """Build a DataFrame with prompt metadata and generation stats for one split.""" | |
| topic_names = [topics[l] for l in labels] | |
| # n_gen_steps is a rough proxy for how much the model generated before stopping | |
| n_gen_steps = [hs.shape[0] for hs in hidden_states_for_ref_layer] | |
| return pd.DataFrame( | |
| { | |
| "prompt": texts, | |
| "generation": gens, | |
| "label": labels.tolist(), | |
| "topic": topic_names, | |
| "char_len": [len(t) for t in texts], | |
| "word_count": [len(t.split()) for t in texts], | |
| "n_gen_steps": n_gen_steps, | |
| } | |
| ) | |
| def print_results(preds, labels, per_topic_metrics): | |
| """Print overall accuracy and per-topic precision/recall/F1.""" | |
| acc = (preds == labels).mean() | |
| print(f" overall accuracy: {acc:.4f}") | |
| for topic, metrics in per_topic_metrics.items(): | |
| print( | |
| f" {topic:12s} " | |
| f"prec={metrics['precision']:.3f} " | |
| f"rec={metrics['recall']:.3f} " | |
| f"f1={metrics['f1']:.3f} " | |
| f"({metrics['n_test']} samples)" | |
| ) | |
Xet Storage Details
- Size:
- 6.92 kB
- Xet hash:
- fb7a7441c8de3cfb579fa371de88e96ea23f22073fdfd89a3c7e55627bfdbf2f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.