GOVINDFROM's picture
Automated push from Zaratan
dbbc6f9 verified
Raw History Blame Contribute Delete
3.46 kB
"""text8: the standard character-level language-modelling benchmark.
100M characters of cleaned English Wikipedia, vocabulary of 27 symbols (a-z and
space). The conventional split is the first 90M characters for training, the
next 5M for validation and the last 5M for test; it is used unchanged here so
bits-per-character is comparable with published numbers.
This is the task that separates the two failure modes of parallel decoding.
Addition has unique answers, so every conditional is a point mass and all of the
damage comes from marginals that were never trained at high mask ratios. Natural
text has both kinds of position at once: closing a word, a suffix or a function
word is nearly deterministic given the context, while the choice of the next
content word carries real entropy. A method that claims to work "across all
things" has to handle a sequence in which both appear together.
"""
import os
import numpy as np
URL = "http://mattmahoney.net/dc/text8.zip"
VOCAB = " abcdefghijklmnopqrstuvwxyz" # 27 symbols, exactly as distributed
# the conventional text8 split, in characters
N_TRAIN, N_VALID, N_TEST = 90_000_000, 5_000_000, 5_000_000
class Text8Tokenizer:
"""Character vocabulary plus the absorbing [MASK] state.
[MASK] is appended after the 27 real characters, so ids 0..26 are the data
symbols and id 27 is the absorbing state. No padding symbol is needed: every
training sequence is a fixed-length contiguous window of the corpus.
"""
def __init__(self):
self.itos = list(VOCAB) + ["[MASK]"]
self.stoi = {c: i for i, c in enumerate(self.itos)}
self.mask = self.stoi["[MASK]"]
self.pad = self.mask # unused; kept for interface compatibility
def __len__(self):
return len(self.itos)
def encode(self, s):
return np.array([self.stoi[c] for c in s if c in self.stoi], dtype=np.int64)
def decode(self, ids):
return "".join(self.itos[int(i)] for i in ids)
def _raw(root):
path = os.path.join(root, "text8")
if not os.path.exists(path):
raise FileNotFoundError(
f"{path} not found. Fetch it once with:\n"
f" mkdir -p {root} && cd {root} && curl -sLO {URL} && unzip -q text8.zip")
with open(path, "r") as f:
return f.read()
def load_text8(root="data/text8", seq_len=256):
"""Return (train, valid, test) integer arrays of shape (n_windows, seq_len).
Windows are non-overlapping and contiguous, which keeps the evaluation
splits strictly disjoint from training: no window can straddle a split
boundary, so no test character is ever seen during training.
"""
tok = Text8Tokenizer()
text = _raw(root)
if len(text) < N_TRAIN + N_VALID + N_TEST:
raise ValueError(f"text8 is {len(text)} chars, expected 100,000,000")
ids = tok.encode(text)
tr = ids[:N_TRAIN]
va = ids[N_TRAIN:N_TRAIN + N_VALID]
te = ids[N_TRAIN + N_VALID:N_TRAIN + N_VALID + N_TEST]
def windows(a):
n = len(a) // seq_len
return a[:n * seq_len].reshape(n, seq_len)
return windows(tr), windows(va), windows(te), tok
def batches(arr, bs, seed=0, shuffle=True):
"""Infinite batch iterator over pre-windowed data."""
rng = np.random.default_rng(seed)
n = len(arr)
while True:
idx = rng.permutation(n) if shuffle else np.arange(n)
for i in range(0, n - bs + 1, bs):
yield arr[idx[i:i + bs]]