Text Classification
Transformers
PyTorch
Safetensors
English
marketing_classifier
feature-extraction
fineweb
marketing
content-filtering
data-curation
gemma
embedding
custom_code
Instructions to use marketeam/Fineweb-Classifier-Marketing with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use marketeam/Fineweb-Classifier-Marketing with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="marketeam/Fineweb-Classifier-Marketing", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("marketeam/Fineweb-Classifier-Marketing", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
| """ | |
| Stage 5 β Filter FineWeb by classifier score and write release datasets. | |
| Reads per-dump score parquets produced by Stage 4 and streams through FineWeb | |
| to produce two filtered datasets: | |
| fineweb-marketing top ~8% by Head A percentile (~1.3T tokens) | |
| fineweb-marketing-score-2 top ~35% by Head A percentile (~5.4T tokens) | |
| Filtering is percentile-based, not absolute int_score (spec Β§5.2). The three | |
| score columns (score, int_score, percentile) are kept in every output row | |
| alongside all original FineWeb document columns. | |
| Crash-safe: both output files must exist for a dump to be considered complete; | |
| if either is missing the dump is reprocessed from scratch. | |
| Usage β single dump: | |
| python -m filtering.filter \\ | |
| --scores-dir data/scores \\ | |
| --output-dir data/release \\ | |
| --dump CC-MAIN-2024-10 | |
| Usage β all dumps (discovers via HF Hub): | |
| python -m filtering.filter \\ | |
| --scores-dir data/scores \\ | |
| --output-dir data/release \\ | |
| --all-dumps | |
| Usage β stats only (threshold statistics without streaming FineWeb): | |
| python -m filtering.filter \\ | |
| --scores-dir data/scores \\ | |
| --output-dir data/release \\ | |
| --all-dumps --stats-only | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| import pandas as pd | |
| import pyarrow as pa | |
| import pyarrow.parquet as pq | |
| from datasets import get_dataset_config_names, load_dataset | |
| from tqdm import tqdm | |
| FINEWEB_REPO = "HuggingFaceFW/fineweb" | |
| PRIMARY_PCT = 0.08 # top 8% β FineWeb-Marketing | |
| SECONDARY_PCT = 0.35 # top 35% β FineWeb-Marketing-score-2 | |
| PRIMARY_SUBDIR = "fineweb-marketing" | |
| SECONDARY_SUBDIR = "fineweb-marketing-score-2" | |
| WRITE_CHUNK = 10_000 | |
| # ββ Pure functions (testable without network) ββββββββββββββββββββββββββββββββββ | |
| def compute_percentile_threshold(target_fraction: float) -> float: | |
| """ | |
| Convert a retention fraction to the minimum percentile for kept documents. | |
| Top 8% β 0.92. Raises ValueError for out-of-range input. | |
| """ | |
| if not 0.0 < target_fraction < 1.0: | |
| raise ValueError( | |
| f"target_fraction must be in (0, 1), got {target_fraction!r}" | |
| ) | |
| return 1.0 - target_fraction | |
| def build_score_index( | |
| scores_path: Path, | |
| min_percentile: float, | |
| ) -> dict[str, tuple[float, int, float]]: | |
| """ | |
| Load a Stage 4 score parquet and return {id: (score, int_score, percentile)} | |
| for all rows with percentile >= min_percentile. | |
| Uses PyArrow predicate pushdown to avoid loading below-threshold rows into RAM. | |
| The threshold is converted to float32 space before filtering to stay consistent | |
| with the stored float32 column and avoid boundary mismatches. | |
| """ | |
| # Convert to float32 space so the filter matches the stored column type | |
| threshold_f32 = float(np.float32(min_percentile)) | |
| table = pq.read_table( | |
| scores_path, | |
| columns=["id", "score", "int_score", "percentile"], | |
| filters=[("percentile", ">=", threshold_f32)], | |
| ) | |
| ids = table.column("id").to_pylist() | |
| scores = table.column("score").to_pylist() | |
| int_scores = table.column("int_score").to_pylist() | |
| percentiles = table.column("percentile").to_pylist() | |
| return { | |
| id_: (float(score), int(int_score), float(pct)) | |
| for id_, score, int_score, pct in zip(ids, scores, int_scores, percentiles) | |
| } | |
| def route_doc( | |
| doc_id: str, | |
| score_index: dict[str, tuple[float, int, float]], | |
| primary_threshold: float, | |
| ) -> tuple[str, tuple[float, int, float]] | None: | |
| """ | |
| Classify a document as "primary" or "secondary" and return its score entry. | |
| "primary" β in index with percentile >= primary_threshold | |
| "secondary" β in index with percentile < primary_threshold | |
| None β not in index (below secondary threshold) | |
| The score_index is already filtered to >= secondary_threshold, so any ID | |
| absent from it is automatically excluded. Returning the entry alongside | |
| the tier avoids a second dict lookup at the call site. | |
| """ | |
| entry = score_index.get(doc_id) | |
| if entry is None: | |
| return None | |
| _, _, pct = entry | |
| tier = "primary" if pct >= primary_threshold else "secondary" | |
| return tier, entry | |
| def compute_threshold_stats( | |
| scores_path: Path, | |
| primary_threshold: float, | |
| secondary_threshold: float, | |
| ) -> dict[str, Any]: | |
| """ | |
| Compute per-dump doc counts and minimum score at each percentile cutoff. | |
| The returned int_score distributions reveal the equivalent int_score | |
| thresholds for FineWeb-EDU users (written to threshold_summary.json). | |
| """ | |
| table = pq.read_table(scores_path, columns=["score", "int_score", "percentile"]) | |
| df = table.to_pandas() | |
| p_mask = df["percentile"] >= primary_threshold | |
| s_mask = df["percentile"] >= secondary_threshold | |
| def _int_counts(mask: pd.Series) -> dict[str, int]: | |
| if not mask.any(): | |
| return {} | |
| return { | |
| str(int(k)): int(v) | |
| for k, v in df.loc[mask, "int_score"].value_counts().sort_index().items() | |
| } | |
| return { | |
| "total_docs": int(len(df)), | |
| "primary_docs": int(p_mask.sum()), | |
| "secondary_docs": int(s_mask.sum()), | |
| "primary_min_score": ( | |
| float(df.loc[p_mask, "score"].min()) if p_mask.any() else None | |
| ), | |
| "secondary_min_score": ( | |
| float(df.loc[s_mask, "score"].min()) if s_mask.any() else None | |
| ), | |
| "primary_int_score_counts": _int_counts(p_mask), | |
| "secondary_int_score_counts": _int_counts(s_mask), | |
| } | |
| # ββ Buffered parquet writer ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class _TierWriter: | |
| """Accumulates rows in memory and flushes to a single parquet file in chunks.""" | |
| def __init__(self, path: Path, chunk_size: int = WRITE_CHUNK) -> None: | |
| self._path = path | |
| self._chunk_size = chunk_size | |
| self._buf: list[dict[str, Any]] = [] | |
| self._writer: pq.ParquetWriter | None = None | |
| self._schema: pa.Schema | None = None | |
| self.count: int = 0 | |
| def add(self, row: dict[str, Any]) -> None: | |
| self._buf.append(row) | |
| self.count += 1 | |
| if len(self._buf) >= self._chunk_size: | |
| self._flush() | |
| def _flush(self) -> None: | |
| if not self._buf: | |
| return | |
| columns = list(self._buf[0].keys()) | |
| table = pa.Table.from_arrays( | |
| [pa.array([r[c] for r in self._buf]) for c in columns], | |
| names=columns, | |
| ) | |
| if self._writer is None: | |
| self._schema = table.schema | |
| self._writer = pq.ParquetWriter( | |
| self._path, self._schema, compression="snappy" | |
| ) | |
| else: | |
| table = table.cast(self._schema) | |
| self._writer.write_table(table) | |
| self._buf.clear() | |
| def close(self) -> None: | |
| """Flush remaining rows and close. Writes a zero-row file if no data was added.""" | |
| self._flush() | |
| if self._writer is not None: | |
| self._writer.close() | |
| else: | |
| _write_empty(self._path) | |
| def _write_empty(path: Path) -> None: | |
| """Write a zero-row parquet to mark a dump as complete when no docs pass the threshold.""" | |
| schema = pa.schema([ | |
| ("id", pa.string()), | |
| ("dump", pa.string()), | |
| ("score", pa.float32()), | |
| ("int_score", pa.int8()), | |
| ("percentile", pa.float32()), | |
| ]) | |
| pq.write_table( | |
| pa.table( | |
| {f: pa.array([], type=t) for f, t in zip(schema.names, schema.types)}, | |
| schema=schema, | |
| ), | |
| path, | |
| ) | |
| # ββ Main filter function βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def filter_dump( | |
| dump: str, | |
| scores_dir: Path, | |
| output_dir: Path, | |
| primary_pct: float = PRIMARY_PCT, | |
| secondary_pct: float = SECONDARY_PCT, | |
| hf_dataset: str = FINEWEB_REPO, | |
| ) -> dict[str, Any]: | |
| """ | |
| Filter one CC-MAIN dump into the primary and secondary release datasets. | |
| Phase 1: load the Stage 4 score parquet, build an in-memory index for all | |
| documents above the secondary percentile threshold. | |
| Phase 2: stream through FineWeb once, routing each matching document to the | |
| primary and/or secondary output writer. | |
| Both output files must exist for the dump to be considered complete. | |
| Temp files are used for atomic rename; stale temps from a prior crash are | |
| removed at the start of each run. | |
| """ | |
| primary_out = output_dir / PRIMARY_SUBDIR | |
| secondary_out = output_dir / SECONDARY_SUBDIR | |
| primary_file = primary_out / f"{dump}.parquet" | |
| secondary_file = secondary_out / f"{dump}.parquet" | |
| if primary_file.exists() and secondary_file.exists(): | |
| print(f" {dump}: already done, skipping.") | |
| return {"dump": dump, "status": "skipped"} | |
| scores_path = scores_dir / f"{dump}.parquet" | |
| if not scores_path.exists(): | |
| print(f" {dump}: no score file found at {scores_path}, skipping.") | |
| return {"dump": dump, "status": "no_scores"} | |
| primary_out.mkdir(parents=True, exist_ok=True) | |
| secondary_out.mkdir(parents=True, exist_ok=True) | |
| primary_threshold = compute_percentile_threshold(primary_pct) | |
| secondary_threshold = compute_percentile_threshold(secondary_pct) | |
| score_index = build_score_index(scores_path, secondary_threshold) | |
| print( | |
| f" {dump}: {len(score_index):,} docs pass secondary threshold " | |
| f"({secondary_pct:.0%})" | |
| ) | |
| primary_tmp = primary_file.with_suffix(".parquet.tmp") | |
| secondary_tmp = secondary_file.with_suffix(".parquet.tmp") | |
| for tmp in (primary_tmp, secondary_tmp): | |
| tmp.unlink(missing_ok=True) | |
| primary_writer = _TierWriter(primary_tmp, chunk_size=WRITE_CHUNK) | |
| secondary_writer = _TierWriter(secondary_tmp, chunk_size=WRITE_CHUNK) | |
| streamed = 0 | |
| ds = load_dataset(hf_dataset, name=dump, split="train", streaming=True) | |
| try: | |
| for doc in tqdm(ds, desc=f"Filtering {dump}"): | |
| streamed += 1 | |
| routed = route_doc(doc["id"], score_index, primary_threshold) | |
| if routed is None: | |
| continue | |
| tier, (score, int_score, percentile) = routed | |
| row = dict(doc) | |
| row["score"] = np.float32(score) | |
| row["int_score"] = np.int8(int_score) | |
| row["percentile"] = np.float32(percentile) | |
| if tier == "primary": | |
| primary_writer.add(row) | |
| secondary_writer.add(row) | |
| finally: | |
| primary_writer.close() | |
| secondary_writer.close() | |
| # Only reached if streaming completed without exception | |
| primary_tmp.rename(primary_file) | |
| secondary_tmp.rename(secondary_file) | |
| print( | |
| f" {dump}: {primary_writer.count:,} primary, " | |
| f"{secondary_writer.count:,} secondary " | |
| f"(of {streamed:,} streamed)" | |
| ) | |
| return { | |
| "dump": dump, | |
| "status": "done", | |
| "primary": primary_writer.count, | |
| "secondary": secondary_writer.count, | |
| "streamed": streamed, | |
| } | |
| # ββ Threshold summary ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def write_threshold_summary( | |
| per_dump_stats: list[dict[str, Any]], | |
| primary_pct: float, | |
| secondary_pct: float, | |
| output_path: Path, | |
| ) -> None: | |
| """ | |
| Write a JSON file documenting per-dump score and int_score statistics at | |
| both percentile cutoffs. | |
| This documents the equivalent int_score thresholds so FineWeb-EDU users | |
| can compare datasets without re-running inference. | |
| """ | |
| output_path.write_text( | |
| json.dumps( | |
| { | |
| "primary_fraction": primary_pct, | |
| "secondary_fraction": secondary_pct, | |
| "primary_percentile_threshold": compute_percentile_threshold(primary_pct), | |
| "secondary_percentile_threshold": compute_percentile_threshold( | |
| secondary_pct | |
| ), | |
| "per_dump": per_dump_stats, | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| print(f"Threshold summary β {output_path}") | |
| # ββ Dump discovery βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_cc_dumps(hf_dataset: str = FINEWEB_REPO) -> list[str]: | |
| """Return sorted list of CC-MAIN-* config names for the given HF dataset.""" | |
| configs = get_dataset_config_names(hf_dataset) | |
| return sorted(c for c in configs if c.startswith("CC-MAIN-")) | |
| # ββ CLI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| ap = argparse.ArgumentParser( | |
| description="Stage 5: filter FineWeb by Head A score and write release datasets." | |
| ) | |
| ap.add_argument( | |
| "--scores-dir", | |
| type=Path, | |
| required=True, | |
| help="Directory with per-dump score parquets from Stage 4", | |
| ) | |
| ap.add_argument( | |
| "--output-dir", | |
| type=Path, | |
| required=True, | |
| help=( | |
| "Root output directory. Two subdirectories are created: " | |
| f"{PRIMARY_SUBDIR!r} and {SECONDARY_SUBDIR!r}." | |
| ), | |
| ) | |
| dump_group = ap.add_mutually_exclusive_group(required=True) | |
| dump_group.add_argument("--dump", help="Single CC-MAIN dump name") | |
| dump_group.add_argument( | |
| "--all-dumps", | |
| action="store_true", | |
| help="Discover and process all CC-MAIN configs from HF Hub", | |
| ) | |
| dump_group.add_argument( | |
| "--dump-list", | |
| type=Path, | |
| help="Plain-text file with one dump name per line", | |
| ) | |
| ap.add_argument( | |
| "--primary-pct", | |
| type=float, | |
| default=PRIMARY_PCT, | |
| help=f"Primary retention fraction (default {PRIMARY_PCT})", | |
| ) | |
| ap.add_argument( | |
| "--secondary-pct", | |
| type=float, | |
| default=SECONDARY_PCT, | |
| help=f"Secondary retention fraction (default {SECONDARY_PCT})", | |
| ) | |
| ap.add_argument("--hf-dataset", default=FINEWEB_REPO) | |
| ap.add_argument( | |
| "--stats-only", | |
| action="store_true", | |
| help="Print threshold statistics from score parquets only (no FineWeb streaming)", | |
| ) | |
| args = ap.parse_args() | |
| if args.dump: | |
| dumps = [args.dump] | |
| elif args.dump_list: | |
| dumps = [ | |
| d.strip() | |
| for d in args.dump_list.read_text().splitlines() | |
| if d.strip() | |
| ] | |
| else: | |
| print(f"Discovering CC-MAIN dumps from {args.hf_dataset} β¦") | |
| dumps = get_cc_dumps(args.hf_dataset) | |
| print(f"Found {len(dumps)} CC-MAIN dumps.") | |
| primary_threshold = compute_percentile_threshold(args.primary_pct) | |
| secondary_threshold = compute_percentile_threshold(args.secondary_pct) | |
| args.output_dir.mkdir(parents=True, exist_ok=True) | |
| summary_path = args.output_dir / "threshold_summary.json" | |
| if args.stats_only: | |
| per_dump_stats = [] | |
| for d in dumps: | |
| p = args.scores_dir / f"{d}.parquet" | |
| if not p.exists(): | |
| print(f" {d}: no score file, skipping.") | |
| continue | |
| stats = compute_threshold_stats(p, primary_threshold, secondary_threshold) | |
| stats["dump"] = d | |
| per_dump_stats.append(stats) | |
| print( | |
| f" {d}: {stats['primary_docs']:,} primary / " | |
| f"{stats['secondary_docs']:,} secondary of " | |
| f"{stats['total_docs']:,} total" | |
| ) | |
| write_threshold_summary( | |
| per_dump_stats, args.primary_pct, args.secondary_pct, summary_path | |
| ) | |
| else: | |
| all_stats = [] | |
| for d in dumps: | |
| result = filter_dump( | |
| dump=d, | |
| scores_dir=args.scores_dir, | |
| output_dir=args.output_dir, | |
| primary_pct=args.primary_pct, | |
| secondary_pct=args.secondary_pct, | |
| hf_dataset=args.hf_dataset, | |
| ) | |
| # Include stats for both newly processed and already-skipped dumps | |
| # so threshold_summary.json is complete even on resumed runs. | |
| if result.get("status") in ("done", "skipped"): | |
| score_p = args.scores_dir / f"{d}.parquet" | |
| if score_p.exists(): | |
| dump_stats = compute_threshold_stats( | |
| score_p, primary_threshold, secondary_threshold | |
| ) | |
| dump_stats["dump"] = d | |
| all_stats.append(dump_stats) | |
| if all_stats: | |
| write_threshold_summary( | |
| all_stats, args.primary_pct, args.secondary_pct, summary_path | |
| ) | |
| print("Stage 5 complete.") | |