balochi-tokenizers / Code /Analyze_Vocab_Pruning.py
hafeez007's picture
Update tokenizer models and README
e899795 verified
Raw
History Blame Contribute Delete
4.81 kB
# -*- coding: utf-8 -*-
"""
Vocabulary Pruning Analyzer
===========================
This script determines the optimal vocabulary size by analyzing token frequencies
across the entire training corpus. It calculates the cumulative frequency distribution
to find the exact vocabulary size needed to cover 99% or 99.5% of the text, allowing
us to safely prune the "long tail" of rare tokens.
"""
import os
import sys
import time
import json
from collections import Counter
# Fix Windows console encoding
if sys.platform == "win32":
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
try:
from tokenizers import Tokenizer
import sentencepiece as spm
except ImportError:
print("Please install tokenizers and sentencepiece: pip install tokenizers sentencepiece")
sys.exit(1)
# Configuration
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CORPUS_PATH = os.path.join(SCRIPT_DIR, "Output", "Ablation", "training_corpus_normalized.txt")
TOKENIZER_PATH = os.path.join(SCRIPT_DIR, "Output", "Ablation", "Models", "sentencepiece_64000", "sentencepiece_64000.model")
def main():
print("=" * 80)
print(" βœ‚οΈ Vocabulary Pruning Analyzer β€” Cumulative Frequency Thresholding")
print("=" * 80)
if not os.path.exists(CORPUS_PATH):
print(f"❌ Corpus not found at {CORPUS_PATH}")
return
if not os.path.exists(TOKENIZER_PATH):
print(f"❌ Tokenizer not found at {TOKENIZER_PATH}")
return
print(f"\n[1] Loading SentencePiece Tokenizer (64K)...")
sp = spm.SentencePieceProcessor()
sp.load(TOKENIZER_PATH)
vocab_size = sp.get_piece_size()
print(f" βœ“ Loaded vocabulary size: {vocab_size:,}")
print(f"\n[2] Tokenizing Corpus & Counting Frequencies...")
print(f" (This may take a few minutes for a ~370MB corpus)")
token_counts = Counter()
total_tokens = 0
lines_processed = 0
start_time = time.time()
# Read and tokenize line by line to save memory
with open(CORPUS_PATH, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
# Encode as IDs
ids = sp.encode_as_ids(line)
token_counts.update(ids)
total_tokens += len(ids)
lines_processed += 1
if lines_processed % 500000 == 0:
print(f" ... processed {lines_processed:,} lines")
elapsed = time.time() - start_time
print(f" βœ“ Tokenization complete in {elapsed:.1f}s")
print(f" βœ“ Total tokens in corpus: {total_tokens:,}")
print(f" βœ“ Unique tokens utilized: {len(token_counts):,}")
if total_tokens == 0:
print("❌ No tokens generated. Corpus might be empty.")
return
print(f"\n[3] Calculating Cumulative Frequency...")
# Sort tokens by frequency (highest first)
sorted_tokens = sorted(token_counts.items(), key=lambda x: x[1], reverse=True)
cumulative_sum = 0
thresholds = [0.90, 0.95, 0.99, 0.995, 0.999]
threshold_idx = 0
results = []
for rank, (token_id, count) in enumerate(sorted_tokens, 1):
cumulative_sum += count
cumulative_pct = cumulative_sum / total_tokens
while threshold_idx < len(thresholds) and cumulative_pct >= thresholds[threshold_idx]:
results.append({
"coverage": thresholds[threshold_idx],
"vocab_size": rank,
"token_id": token_id,
"count": count
})
threshold_idx += 1
print("\n" + "=" * 60)
print(" 🎯 RECOMMENDED PRUNING THRESHOLDS")
print("=" * 60)
print(f"{'Coverage Target':<20} | {'Required Vocab Size':<20} | {'Tokens Pruned':<15}")
print("-" * 60)
for res in results:
target_pct = f"{res['coverage']*100:.1f}%"
req_vocab = f"{res['vocab_size']:,}"
pruned = f"{vocab_size - res['vocab_size']:,}"
print(f"{target_pct:<20} | {req_vocab:<20} | {pruned:<15}")
print("-" * 60)
# Summary advice
t99 = next((r for r in results if r['coverage'] == 0.99), None)
if t99:
recommended_size = (t99['vocab_size'] // 1000 + 1) * 1000 # round up to nearest thousand
print(f"\nπŸ’‘ CONCLUSION:")
print(f"To achieve 99.0% coverage (the AraToken paper recommendation),")
print(f"you only need the top ~{t99['vocab_size']:,} tokens.")
print(f"You can safely train a new optimal tokenizer with vocab_size={recommended_size:,}")
print(f"This will drop ~{vocab_size - recommended_size:,} useless tokens, shrinking the Gemma embedding matrix.")
if __name__ == "__main__":
main()