FineWeb を用いたストリーミング、フィルタリング、重複排除、トークン化、大規模ウェブコーパス分析のコーディングハンズオン
MarkTechPost は、大規模なデータセットをダウンロードせずに FineWeb データセットのサンプルをストリーミングし、スキーマやメタデータを調査するチュートリアルを提供しています。また、品質フィルタリングパイプラインの再現、MinHash による重複検出、GPT-2 トークナイザーを用いたトークン数検証、および有用な分析結果の生成方法を解説しています。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
本チュートリアルでは、高度なハンズオンワークフローを通じて FineWeb データセットを探ります。多テラバイト規模の完全なコーパスをダウンロードすることなく、管理可能なサンプルをストリーミングし、そのスキーマとメタデータを調査します。また、URL、言語、言語スコア、トークン数といった主要フィールドを分析します。さらに、FineWeb の品質フィルタリングパイプラインの簡略化版を再現し、MinHash ベースの近接重複検出を適用し、GPT-2 トークナイザーを用いてトークン数を検証します。そして、ドメイン、言語スコア、文書長、トークナイザーの効率性に関する有用な分析結果を生成します。
コードをコピーしました。別のブラウザを使用してください
import subprocess, sys
def pip(*pkgs):
subprocess.run([sys.executable, "-m", "pip", "install", "-q", *pkgs], check=True)
pip("datasets>=2.19", "datasketch", "tiktoken", "pandas", "matplotlib", "tqdm")
import re, math, random, collections
from urllib.parse import urlparse
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
from datasets import load_dataset
random.seed(0); np.random.seed(0)
pd.set_option("display.max_colwidth", 90)
ストリーミング、分析、重複排除、トークン化、可視化に必要なすべてのライブラリをインストールすることから始めます。FineWeb ドキュメントを処理し、表形式データを扱うために必要なコア Python パッケージを読み込みます。また、結果の一貫性を保ち、調査を容易にするため、乱数のシードと表示オプションを設定します。
コードをコピーしました。別のブラウザを使用してください
N_DOCS = 3000
print(f"Streaming {N_DOCS} docs from FineWeb sample-10BT ...")
stream = load_dataset(
"HuggingFaceFW/fineweb",
name="sample-10BT",
split="train",
streaming=True,
)
docs = []
for i, doc in enumerate(tqdm(stream, total=N_DOCS)):
docs.append(doc)
if i + 1 >= N_DOCS:
break
df = pd.DataFrame(docs)
print("\nColumns:", list(df.columns))
print(df[["url", "language", "language_score", "token_count"]].head(5))
ex = docs[0]
print("\n--- Example record (fields) ---")
for k, v in ex.items():
preview = (v[:120] + "…") if isinstance(v, str) and len(v) > 120 else v
print(f"{k:>16}: {preview}")
FineWeb の sample-10BT サブセットから、フルデータセットをダウンロードすることなく、固定数のドキュメントをストリーミングします。ストリーミングされたレコードを DataFrame に変換し、URL、言語、言語スコア、トークン数といった主要なメタデータフィールドを検証します。また、完全な例レコードを出力して、データセットの構造をより深く理解できるようにしています。
Copy CodeCopiedUse a different Browser
WORD = re.compile(r"\b\w+\b")
def gopher_quality(text):
words = WORD.findall(text)
n = len(words)
if n < 50 or n > 100_000:
return False, "word_count_out_of_range"
mean_len = sum(len(w) for w in words) / n
if mean_len < 3 or mean_len > 10:
return False, "bad_mean_word_length"
if (text.count("#") + text.count("...")) / n > 0.1:
return False, "too_many_symbols"
lines = text.split("\n")
if lines and sum(l.lstrip().startswith(("•", "-", "*")) for l in lines) / len(lines) > 0.9:
return False, "mostly_bullets"
stops = {"the", "be", "to", "of", "and", "that", "have", "with"}
if len(stops & {w.lower() for w in words}) < 2:
return False, "too_few_stopwords"
return True, "ok"
def c4_quality(text):
lines = [l for l in text.split("\n") if l.strip()]
if not lines:
return False, "empty"
low = text.lower()
for bad in ("lorem ipsum", "javascript is disabled"):
if bad in low:
return False, f"boilerplate:{bad}"
if text.count("{") > 0 and text.count("{") / max(len(lines), 1) > 0.5:
return False, "too_many_braces"
return True, "ok"
def fineweb_custom(text):
lines = [l.strip() for l in text.split("\n") if l.strip()]
if not lines:
return False, "empty"
dup_frac = 1 - len(set(lines)) / len(lines)
if dup_frac > 0.3:
return False, "duplicated_lines"
short_frac = sum(len(l) < 30 for l in lines) / len(lines)
if short_frac > 0.67 and len(lines) > 5:
return False, "list_like"
return True, "ok"
results = []
for d in docs:
t = d["text"]
g_ok, g_r = gopher_quality(t)
c_ok, c_r = c4_quality(t)
f_ok, f_r = fineweb_custom(t)
reason = "kept" if (g_ok and c_ok and f_ok) else (g_r if not g_ok else c_r if not c_ok else f_r)
results.append(reason)
filter_summary = pd.Series(results).value_counts()
print("\n--- Quality-filter outcomes on already-clean FineWeb data ---")
print("(Most pass: FineWeb is pre-filtered. Rejections show what the rules catch.)")
print(filter_summary)
Gopher スタイル、C4 スタイル、およびカスタムのテキストクリーニングヒューリスティックを用いて、FineWeb の品質フィルターの簡易版を再構築します。各ドキュメントについて、異常な単語数、不良な単語統計情報、定型文の繰り返し、重複行、リスト状の構造といった問題がないか確認し、既にクリーンアップされた FineWeb サンプルの品質を理解するために、これらのフィルターに合格・不合格となったドキュメント数を要約します。
Copy CodeCopiedUse a different Browser
from datasketch import MinHash, MinHashLSH
def shingles(text, k=5):
toks = WORD.findall(text.lower())
return {" ".join(toks[i:i+k]) for i in range(max(len(toks) - k + 1, 1))}
NUM_PERM = 128
THRESHOLD = 0.7
lsh = MinHashLSH(threshold=THRESHOLD, num_perm=NUM_PERM)
minhashes = {}
for idx, d in enumerate(tqdm(docs, desc="MinHashing")):
m = MinHash(num_perm=NUM_PERM)
for s in shingles(d["text"]):
m.update(s.encode("utf8"))
minhashes[idx] = m
lsh.insert(str(idx), m)
dup_pairs = set()
for idx, m in minhashes.items():
for cand in lsh.query(m):
c = int(cand)
if c != idx:
dup_pairs.add(tuple(sorted((idx, c))))
print(f"\nFound {len(dup_pairs)} near-duplicate pairs (Jaccard ≥ {THRESHOLD}).")
if dup_pairs:
a, b = next(iter(dup_pairs))
j = minhashes[a].jaccard(minhashes[b])
print(f"Example pair (estimated Jaccard ≈ {j:.2f}):")
print(" DOC A:", docs[a]["text"][:160].replace("\n", " "), "…")
print(" DOC B:", docs[b]["text"][:160].replace("\n", " "), "…")
else:
print("No near-dupes in this slice — expected, since FineWeb is dedup'd per crawl.")
私たちは、大規模なウェブコーパスが重複または非常に類似した文書をどのように識別するかを近似するために、MinHash を用いたニア重複検出を実装しています。各文書を単語のシャングルに変換し、MinHash 署名を生成して局所感度ハッシュ(Locality Sensitive Hashing)でインデックス化します。その後、ニア重複する文書ペアを検索し、類似したテキストが見つかった場合は例を表示します。
コードをコピー
コピー済み
別のブラウザを使用してください
import tiktoken
enc = tiktoken.get_encoding("gpt2")
check = docs[:200]
recomputed = [len(enc.encode(d["text"])) for d in tqdm(check, desc="Tokenizing")]
stored = [d["token_count"] for d in check]
diffs = np.array(recomputed) - np.array(stored)
print(f"\n--- Verifying token_count field (gpt2) on 200 docs ---")
print(f"Mean abs diff vs stored token_count: {np.abs(diffs).mean():.2f} tokens")
print(f"Exact matches: {(diffs == 0).mean()*100:.0f}% (small drift = tokenizer version)")
df["chars_per_token"] = df["text"].str.len() / df["token_count"].clip(lower=1)
print(f"Avg characters per token: {df['chars_per_token'].mean():.2f}")
データセットの token_count フィールドを、tiktoken トークナイザーを使用して GPT-2 のトークン数を再計算することで検証します。再計算したトークン数と保存された値を比較し、その平均差を測定します。また、サンプリングされたドキュメント全体におけるトークナイザーの効率性を理解するために、1 トークンあたりの文字数も計算します。
コードをコピー
コピー済み
別のブラウザを使用してください
df["domain"] = df["url"].apply(lambda u: urlparse(u).netloc.replace("www.", "") if isinstance(u, str) else "?")
top_domains = df["domain"].value_counts().head(15)
print("\n--- Top 15 domains in sample ---")
print(top_domains)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].hist(df["token_count"].clip(upper=4000), bins=50, color="#7b2d26")
axes[0, 0].set_title("Token count per document (gpt2)")
axes[0, 0].set_xlabel("tokens"); axes[0, 0].set_ylabel("docs")
axes[0, 1].hist(df["language_score"], bins=40, color="#2d5d7b")
axes[0, 1].axvline(0.65, color="red", ls="--", label="FineWeb cutoff 0.65")
axes[0, 1].set_title("fastText English language score")
axes[0, 1].set_xlabel("score"); axes[0, 1].legend()
axes[1, 0].hist(df["chars_per_token"].clip(upper=8), bins=40, color="#3f7b2d")
axes[1, 0].set_title("Characters per token (compression)")
axes[1, 0].set_xlabel("chars / token")
top_domains.iloc[::-1].plot(kind="barh", ax=axes[1, 1], color="#7b5d2d")
axes[1, 1].set_title("Top domains")
plt.tight_layout()
plt.show()
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print(f"Docs streamed : {len(df):,}")
print(f"Total gpt2 tokens : {df['token_count'].sum():,}")
print(f"Median tokens/doc : {int(df['token_count'].median())}")
print(f"Unique domains : {df['domain'].nunique():,}")
print(f"Mean language_score : {df['language_score'].mean():.3f}")
print(f"Near-duplicate pairs : {len(dup_pairs)}")
print(f"Docs flagged by filters : {(pd.Series(results) != 'kept').sum()} / {len(results)}")
print("\nNext steps:")
print(" • Swap name='sample-10BT' for a real crawl, e.g. name='CC-MAIN-2024-10'")
print(" • Raise N_DOCS for stronger statistics")
print(" • Use the full datatrove pipeline to reproduce FineWeb end-to-end")
URL からドメイン名を抽出し、FineWeb サンプルに存在する最も頻度の高いドメインを特定します。トークン数の分布、言語スコアの分布、1 トークンあたりの文字数、上位ドメインの可視化を作成します。最後に、ストリーミングされた文書の要約、総トークン数、中央値長さ、一意なドメイン数、言語品質、重複カウント、フィルタリング結果をコンパクトに出力して完了です。
結論として、FineWeb などの大規模ウェブデータセットが、言語モデルのトレーニングのためにどのように探索され、フィルタリングされ、重複排除され、分析されるかについて、実践的な理解を深めました。ストリーミングデータを効率的に取り扱い、実際の文書上で品質ヒューリスティック(経験則)を検証し、ほぼ重複するテキストパターンを特定し、本番環境スタイルのトークナイザーを使用してトークンレベルのメタデータを検証しました。このアプローチは、より大規模な FineWeb クロールへのワークフローのスケーリング、より深いコーパス分析の実行、および LLM データセット準備のための高品質な前処理パイプラインの設計に活用できます。
原文を表示
In this tutorial, we explore the FineWeb dataset through an advanced hands-on workflow. We stream a manageable sample of the dataset without downloading the full multi-terabyte corpus, inspect its schema and metadata, and analyze key fields such as URL, language, language score, and token count. We also reproduce simplified versions of FineWeb’s quality-filtering pipeline, apply MinHash-based near-duplicate detection, verify token counts with the GPT-2 tokenizer, and generate useful analytics on domains, language scores, document lengths, and tokenizer efficiency.
Copy CodeCopiedUse a different Browser
import subprocess, sys
def pip(*pkgs):
subprocess.run([sys.executable, "-m", "pip", "install", "-q", *pkgs], check=True)
pip("datasets>=2.19", "datasketch", "tiktoken", "pandas", "matplotlib", "tqdm")
import re, math, random, collections
from urllib.parse import urlparse
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
from datasets import load_dataset
random.seed(0); np.random.seed(0)
pd.set_option("display.max_colwidth", 90)
We begin by installing all required libraries for streaming, analysis, deduplication, tokenization, and visualization. We import the core Python packages needed to process FineWeb documents and work with tabular data. We also set random seeds and display options so that our results remain consistent and easier to inspect.
Copy CodeCopiedUse a different Browser
N_DOCS = 3000
print(f"Streaming {N_DOCS} docs from FineWeb sample-10BT ...")
stream = load_dataset(
"HuggingFaceFW/fineweb",
name="sample-10BT",
split="train",
streaming=True,
)
docs = []
for i, doc in enumerate(tqdm(stream, total=N_DOCS)):
docs.append(doc)
if i + 1 >= N_DOCS:
break
df = pd.DataFrame(docs)
print("\nColumns:", list(df.columns))
print(df[["url", "language", "language_score", "token_count"]].head(5))
ex = docs[0]
print("\n--- Example record (fields) ---")
for k, v in ex.items():
preview = (v[:120] + "…") if isinstance(v, str) and len(v) > 120 else v
print(f"{k:>16}: {preview}")
We stream a fixed number of documents from the FineWeb sample-10BT subset without downloading the full dataset. We convert the streamed records into a DataFrame and inspect key metadata fields, including URL, language, language score, and token count. We also print a complete example record to better understand the dataset’s structure.
Copy CodeCopiedUse a different Browser
WORD = re.compile(r"\b\w+\b")
def gopher_quality(text):
words = WORD.findall(text)
n = len(words)
if n < 50 or n > 100_000:
return False, "word_count_out_of_range"
mean_len = sum(len(w) for w in words) / n
if mean_len < 3 or mean_len > 10:
return False, "bad_mean_word_length"
if (text.count("#") + text.count("...")) / n > 0.1:
return False, "too_many_symbols"
lines = text.split("\n")
if lines and sum(l.lstrip().startswith(("•", "-", "*")) for l in lines) / len(lines) > 0.9:
return False, "mostly_bullets"
stops = {"the", "be", "to", "of", "and", "that", "have", "with"}
if len(stops & {w.lower() for w in words}) < 2:
return False, "too_few_stopwords"
return True, "ok"
def c4_quality(text):
lines = [l for l in text.split("\n") if l.strip()]
if not lines:
return False, "empty"
low = text.lower()
for bad in ("lorem ipsum", "javascript is disabled"):
if bad in low:
return False, f"boilerplate:{bad}"
if text.count("{") > 0 and text.count("{") / max(len(lines), 1) > 0.5:
return False, "too_many_braces"
return True, "ok"
def fineweb_custom(text):
lines = [l.strip() for l in text.split("\n") if l.strip()]
if not lines:
return False, "empty"
dup_frac = 1 - len(set(lines)) / len(lines)
if dup_frac > 0.3:
return False, "duplicated_lines"
short_frac = sum(len(l) < 30 for l in lines) / len(lines)
if short_frac > 0.67 and len(lines) > 5:
return False, "list_like"
return True, "ok"
results = []
for d in docs:
t = d["text"]
g_ok, g_r = gopher_quality(t)
c_ok, c_r = c4_quality(t)
f_ok, f_r = fineweb_custom(t)
reason = "kept" if (g_ok and c_ok and f_ok) else (g_r if not g_ok else c_r if not c_ok else f_r)
results.append(reason)
filter_summary = pd.Series(results).value_counts()
print("\n--- Quality-filter outcomes on already-clean FineWeb data ---")
print("(Most pass: FineWeb is pre-filtered. Rejections show what the rules catch.)")
print(filter_summary)
We recreate simplified versions of FineWeb’s quality filters using Gopher-style, C4-style, and custom text-cleaning heuristics. We check each document for issues such as abnormal word counts, poor word statistics, boilerplate text, repeated lines, and list-like structure. We summarize how many documents pass or fail these filters to understand the quality of the already-cleaned FineWeb sample.
Copy CodeCopiedUse a different Browser
from datasketch import MinHash, MinHashLSH
def shingles(text, k=5):
toks = WORD.findall(text.lower())
return {" ".join(toks[i:i+k]) for i in range(max(len(toks) - k + 1, 1))}
NUM_PERM = 128
THRESHOLD = 0.7
lsh = MinHashLSH(threshold=THRESHOLD, num_perm=NUM_PERM)
minhashes = {}
for idx, d in enumerate(tqdm(docs, desc="MinHashing")):
m = MinHash(num_perm=NUM_PERM)
for s in shingles(d["text"]):
m.update(s.encode("utf8"))
minhashes[idx] = m
lsh.insert(str(idx), m)
dup_pairs = set()
for idx, m in minhashes.items():
for cand in lsh.query(m):
c = int(cand)
if c != idx:
dup_pairs.add(tuple(sorted((idx, c))))
print(f"\nFound {len(dup_pairs)} near-duplicate pairs (Jaccard ≥ {THRESHOLD}).")
if dup_pairs:
a, b = next(iter(dup_pairs))
j = minhashes[a].jaccard(minhashes[b])
print(f"Example pair (estimated Jaccard ≈ {j:.2f}):")
print(" DOC A:", docs[a]["text"][:160].replace("\n", " "), "…")
print(" DOC B:", docs[b]["text"][:160].replace("\n", " "), "…")
else:
print("No near-dupes in this slice — expected, since FineWeb is dedup'd per crawl.")
We implement MinHash-based near-duplicate detection to approximate how large web corpora identify repeated or highly similar documents. We convert each document into word shingles, generate MinHash signatures, and index them with Locality Sensitive Hashing. We then search for near-duplicate document pairs and inspect an example if any similar texts are found.
Copy CodeCopiedUse a different Browser
import tiktoken
enc = tiktoken.get_encoding("gpt2")
check = docs[:200]
recomputed = [len(enc.encode(d["text"])) for d in tqdm(check, desc="Tokenizing")]
stored = [d["token_count"] for d in check]
diffs = np.array(recomputed) - np.array(stored)
print(f"\n--- Verifying token_count field (gpt2) on 200 docs ---")
print(f"Mean abs diff vs stored token_count: {np.abs(diffs).mean():.2f} tokens")
print(f"Exact matches: {(diffs == 0).mean()*100:.0f}% (small drift = tokenizer version)")
df["chars_per_token"] = df["text"].str.len() / df["token_count"].clip(lower=1)
print(f"Avg characters per token: {df['chars_per_token'].mean():.2f}")
We verify the dataset’s token_count field by recomputing GPT-2 token counts with the tiktoken tokenizer. We compare the recomputed token counts with the stored values and measure the average difference between them. We also calculate characters per token to understand tokenizer efficiency across the sampled documents.
Copy CodeCopiedUse a different Browser
df["domain"] = df["url"].apply(lambda u: urlparse(u).netloc.replace("www.", "") if isinstance(u, str) else "?")
top_domains = df["domain"].value_counts().head(15)
print("\n--- Top 15 domains in sample ---")
print(top_domains)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].hist(df["token_count"].clip(upper=4000), bins=50, color="#7b2d26")
axes[0, 0].set_title("Token count per document (gpt2)")
axes[0, 0].set_xlabel("tokens"); axes[0, 0].set_ylabel("docs")
axes[0, 1].hist(df["language_score"], bins=40, color="#2d5d7b")
axes[0, 1].axvline(0.65, color="red", ls="--", label="FineWeb cutoff 0.65")
axes[0, 1].set_title("fastText English language score")
axes[0, 1].set_xlabel("score"); axes[0, 1].legend()
axes[1, 0].hist(df["chars_per_token"].clip(upper=8), bins=40, color="#3f7b2d")
axes[1, 0].set_title("Characters per token (compression)")
axes[1, 0].set_xlabel("chars / token")
top_domains.iloc[::-1].plot(kind="barh", ax=axes[1, 1], color="#7b5d2d")
axes[1, 1].set_title("Top domains")
plt.tight_layout()
plt.show()
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print(f"Docs streamed : {len(df):,}")
print(f"Total gpt2 tokens : {df['token_count'].sum():,}")
print(f"Median tokens/doc : {int(df['token_count'].median())}")
print(f"Unique domains : {df['domain'].nunique():,}")
print(f"Mean language_score : {df['language_score'].mean():.3f}")
print(f"Near-duplicate pairs : {len(dup_pairs)}")
print(f"Docs flagged by filters : {(pd.Series(results) != 'kept').sum()} / {len(results)}")
print("\nNext steps:")
print(" • Swap name='sample-10BT' for a real crawl, e.g. name='CC-MAIN-2024-10'")
print(" • Raise N_DOCS for stronger statistics")
print(" • Use the full datatrove pipeline to reproduce FineWeb end-to-end")
We extract domain names from URLs and identify the most frequent domains present in the FineWeb sample. We create visualizations for token count distribution, language score distribution, characters per token, and top domains. We finish by printing a compact summary of streamed documents, total tokens, median length, unique domains, language quality, duplicate count, and filter results.
In conclusion, we developed a practical understanding of how large-scale web datasets such as FineWeb are explored, filtered, deduplicated, and analyzed for language model training. We worked efficiently with streaming data, tested quality heuristics on real documents, identified near-duplicate text patterns, and validated token-level metadata using a production-style tokenizer. It can be used to scale the workflow to larger FineWeb crawls, perform deeper corpus analysis, and design high-quality preprocessing pipelines for LLM dataset preparation.
Check out the Full Codes with Notebook. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us
The post A Coding Hands-On on FineWeb for Streaming, Filtering, Deduplication, Tokenization, and Large-Scale Web Corpus Analytics appeared first on MarkTechPost.
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み