Upload dataset.py with huggingface_hub
Browse files- dataset.py +17 -46
dataset.py
CHANGED
|
@@ -1,10 +1,4 @@
|
|
| 1 |
-
# dataset.py
|
| 2 |
-
# ============================================================================
|
| 3 |
-
# Turns raw log TEXT into model-ready NUMBERS. Two responsibilities:
|
| 4 |
-
# 1. build_or_load_tokenizer -> trains/loads a BPE tokenizer (text <-> IDs)
|
| 5 |
-
# 2. AutoregressiveLogDataset -> serves (input, target) token windows for
|
| 6 |
-
# next-token-prediction training.
|
| 7 |
-
# ============================================================================
|
| 8 |
import os
|
| 9 |
import torch
|
| 10 |
from torch.utils.data import Dataset
|
|
@@ -15,73 +9,50 @@ from tokenizers.pre_tokenizers import Whitespace
|
|
| 15 |
|
| 16 |
|
| 17 |
def build_or_load_tokenizer(corpus_path, tokenizer_save_path, vocab_size=1000):
|
| 18 |
-
"""
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
substrings (e.g. "sshd", "Failed", "port") become single tokens, while rare
|
| 23 |
-
text stays split into smaller pieces — keeping the vocabulary compact yet
|
| 24 |
-
able to represent ANY input.
|
| 25 |
"""
|
| 26 |
-
# Reuse the cached tokenizer if it exists. IMPORTANT: if you change
|
| 27 |
-
# vocab_size, delete this file so a new tokenizer is trained.
|
| 28 |
if os.path.exists(tokenizer_save_path):
|
| 29 |
return Tokenizer.from_file(tokenizer_save_path)
|
| 30 |
|
| 31 |
-
# unk_token: the symbol emitted for anything the tokenizer can't represent.
|
| 32 |
tokenizer = Tokenizer(BPE(unk_token="<unk>"))
|
| 33 |
-
#
|
| 34 |
-
#
|
| 35 |
-
tokenizer.pre_tokenizer = Whitespace()
|
| 36 |
-
# vocab_size is a CEILING on how many tokens to learn. special_tokens are
|
| 37 |
-
# reserved IDs: <pad> (padding), <unk> (unknown), "\n" (newline — meaningful
|
| 38 |
-
# in logs, where each line is a record).
|
| 39 |
trainer = BpeTrainer(vocab_size=vocab_size, special_tokens=["<pad>", "<unk>", "\n"])
|
| 40 |
-
tokenizer.train([corpus_path], trainer)
|
| 41 |
-
tokenizer.save(tokenizer_save_path)
|
| 42 |
return tokenizer
|
| 43 |
|
| 44 |
|
| 45 |
class AutoregressiveLogDataset(Dataset):
|
| 46 |
-
"""
|
| 47 |
|
| 48 |
-
The core trick of language modeling: the target is the input shifted by one
|
| 49 |
-
position, so at every step the model learns to predict the NEXT token.
|
| 50 |
-
"""
|
| 51 |
def __init__(self, corpus_path, tokenizer, max_seq_len=64, stride=None):
|
| 52 |
self.tokenizer = tokenizer
|
| 53 |
-
# max_seq_len: window length in tokens = how much context the model sees
|
| 54 |
-
# per training example. Must match the model's max_seq_len.
|
| 55 |
self.max_seq_len = max_seq_len
|
| 56 |
-
|
| 57 |
-
# stride
|
| 58 |
-
#
|
| 59 |
-
# redundant dataset (slow to train).
|
| 60 |
-
# stride=max_seq_len -> non-overlapping chunks (least data, fastest).
|
| 61 |
-
# default = half-window -> a good balance of coverage vs. redundancy.
|
| 62 |
self.stride = stride if stride is not None else max_seq_len // 2
|
| 63 |
|
| 64 |
-
#
|
| 65 |
-
# token IDs. (Fine for a small corpus; a large one would stream instead.)
|
| 66 |
with open(corpus_path, "r") as f:
|
| 67 |
raw_text = f.read()
|
| 68 |
self.encoded_tokens = tokenizer.encode(raw_text).ids
|
| 69 |
|
| 70 |
-
# Precompute
|
| 71 |
-
# max_seq_len + 1 tokens (the +1 is the final target). We step by stride.
|
| 72 |
last_start = len(self.encoded_tokens) - self.max_seq_len - 1
|
| 73 |
self.start_positions = list(range(0, max(0, last_start) + 1, self.stride))
|
| 74 |
|
| 75 |
def __len__(self):
|
| 76 |
-
# Number of training windows = number of start positions.
|
| 77 |
return len(self.start_positions)
|
| 78 |
|
| 79 |
def __getitem__(self, idx):
|
| 80 |
-
# Grab a window of max_seq_len + 1 tokens starting at this position.
|
| 81 |
start = self.start_positions[idx]
|
| 82 |
chunk = self.encoded_tokens[start : start + self.max_seq_len + 1]
|
| 83 |
-
#
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
y = torch.tensor(chunk[1:], dtype=torch.long) # targets (next tokens)
|
| 87 |
return x, y
|
|
|
|
| 1 |
+
# file: dataset.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
import torch
|
| 4 |
from torch.utils.data import Dataset
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
def build_or_load_tokenizer(corpus_path, tokenizer_save_path, vocab_size=1000):
|
| 12 |
+
"""Train a BPE tokenizer on the corpus, or load the cached one if present.
|
| 13 |
|
| 14 |
+
NOTE: the cache is keyed only by path — if you change vocab_size, delete the
|
| 15 |
+
saved json first or you'll silently keep the old vocab.
|
|
|
|
|
|
|
|
|
|
| 16 |
"""
|
|
|
|
|
|
|
| 17 |
if os.path.exists(tokenizer_save_path):
|
| 18 |
return Tokenizer.from_file(tokenizer_save_path)
|
| 19 |
|
|
|
|
| 20 |
tokenizer = Tokenizer(BPE(unk_token="<unk>"))
|
| 21 |
+
tokenizer.pre_tokenizer = Whitespace() # split on spaces first so merges stay within words
|
| 22 |
+
# "\n" is reserved as a token because each log line is a record boundary
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
trainer = BpeTrainer(vocab_size=vocab_size, special_tokens=["<pad>", "<unk>", "\n"])
|
| 24 |
+
tokenizer.train([corpus_path], trainer)
|
| 25 |
+
tokenizer.save(tokenizer_save_path)
|
| 26 |
return tokenizer
|
| 27 |
|
| 28 |
|
| 29 |
class AutoregressiveLogDataset(Dataset):
|
| 30 |
+
"""Sliding windows over the token stream for next-token training."""
|
| 31 |
|
|
|
|
|
|
|
|
|
|
| 32 |
def __init__(self, corpus_path, tokenizer, max_seq_len=64, stride=None):
|
| 33 |
self.tokenizer = tokenizer
|
|
|
|
|
|
|
| 34 |
self.max_seq_len = max_seq_len
|
| 35 |
+
# stride = how far the window advances each step. Default half-window is a
|
| 36 |
+
# balance: stride=1 gives ~187k near-duplicate windows (slow), stride=max_seq_len
|
| 37 |
+
# gives no overlap (less coverage).
|
|
|
|
|
|
|
|
|
|
| 38 |
self.stride = stride if stride is not None else max_seq_len // 2
|
| 39 |
|
| 40 |
+
# Small corpus, so just read + encode it once. Would stream for a big one.
|
|
|
|
| 41 |
with open(corpus_path, "r") as f:
|
| 42 |
raw_text = f.read()
|
| 43 |
self.encoded_tokens = tokenizer.encode(raw_text).ids
|
| 44 |
|
| 45 |
+
# Precompute window starts; each needs max_seq_len + 1 tokens (the +1 is the last target)
|
|
|
|
| 46 |
last_start = len(self.encoded_tokens) - self.max_seq_len - 1
|
| 47 |
self.start_positions = list(range(0, max(0, last_start) + 1, self.stride))
|
| 48 |
|
| 49 |
def __len__(self):
|
|
|
|
| 50 |
return len(self.start_positions)
|
| 51 |
|
| 52 |
def __getitem__(self, idx):
|
|
|
|
| 53 |
start = self.start_positions[idx]
|
| 54 |
chunk = self.encoded_tokens[start : start + self.max_seq_len + 1]
|
| 55 |
+
# y is x shifted by one — at every position the model predicts the next token
|
| 56 |
+
x = torch.tensor(chunk[:-1], dtype=torch.long)
|
| 57 |
+
y = torch.tensor(chunk[1:], dtype=torch.long)
|
|
|
|
| 58 |
return x, y
|