{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Akshar Tokenizer: The Research Pipeline\n", "\n", "This notebook provides the end-to-end, reproducible pipeline for the **Akshar** tokenizer. Akshar is a high-efficiency BPE tokenizer optimized for Romanized Indic scripts (Hinglish/Minglish).\n", "\n", "### Why this matters:\n", "Standard tokenizers (Llama-3, GPT-4) fragment code-mixed Indic words. Akshar achieves a **1.34 fertility score**, outperforming global models by ~20% and specialized Indic models by ~45% on Romanized text.\n", "\n", "### Pipeline Steps:\n", "1. **Scraping**: High-concurrency YouTube comment extraction.\n", "2. **Normalization**: CamelCase splitting, ASCII-only enforcement, and casing redundancy elimination.\n", "3. **Training**: Streaming BPE training on 20M+ lines (No OOM crashes).\n", "4. **Rugged Benchmarking**: Cross-model comparison on unseen test data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install -q yt-dlp tokenizers transformers huggingface_hub pandas tiktoken seaborn tabulate tqdm --break-system-packages" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json, os, re, time, random\n", "import pandas as pd\n", "from concurrent.futures import ThreadPoolExecutor\n", "from tqdm.auto import tqdm\n", "from tokenizers import Tokenizer, models, pre_tokenizers, trainers, decoders, normalizers, Regex\n", "import tiktoken\n", "from transformers import AutoTokenizer\n", "\n", "# CONFIGURATION\n", "VOCAB_SIZE = 32768\n", "CORPUS_PATH = \"data/cleaned/akshar_corpus.jsonl\"\n", "TARGET_CHANNELS = [\"[REDACTED]\"]\n", "os.makedirs(\"data/cleaned\", exist_ok=True)\n", "os.makedirs(\"data/tokenizer\", exist_ok=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. The Akshar Cleaning Pipeline\n", "\n", "This pipeline splits CamelCase (hashtags) and enforces Romanization to keep the vocabulary high-quality." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def clean_line(text):\n", " if not text or len(text) < 5: return None\n", " \n", " # Split CamelCase & Hashtags (e.g. #BiharDecides -> Bihar Decides)\n", " text = re.sub(r'([a-z])([A-Z])', r'\\1 \\2', text)\n", " text = text.replace(\"#\", \" \")\n", " \n", " # Strip Emojis & Non-ASCII (Standard Project Rule)\n", " text = re.sub(r'[^\\x00-\\x7F]+', ' ', text)\n", " \n", " # PII & Noise Redaction\n", " text = re.sub(r'http\\S+', '[URL]', text)\n", " text = re.sub(r'\\S+@\\S+', '[EMAIL]', text)\n", " text = re.sub(r'\\d{10}', '[PHONE]', text)\n", " \n", " # Normalize Spacing\n", " text = re.sub(r'\\s+', ' ', text).strip()\n", " return text if len(text) > 5 else None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Tokenizer Training\n", "\n", "We use **Streaming Generators** to prevent memory exhaustion and a **Lowercase Normalizer** to maximize vocabulary efficiency." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def corpus_iterator(path):\n", " if not os.path.exists(path): return\n", " with open(path, \"r\", encoding=\"utf-8\") as f:\n", " for line in f:\n", " try: yield json.loads(line)[\"text\"]\n", " except: continue\n", "\n", "tokenizer = Tokenizer(models.BPE())\n", "\n", "# Akshar Normalizer: ASCII-only + Lowercase\n", "tokenizer.normalizer = normalizers.Sequence([\n", " normalizers.Replace(Regex(r\"[^\\x00-\\x7F]+\"), \"\"),\n", " normalizers.Lowercase()\n", "])\n", "\n", "# Pre-tokenizer: Digits & Punctuation Isolation\n", "tokenizer.pre_tokenizer = pre_tokenizers.Sequence([\n", " pre_tokenizers.Split(Regex(r\"\\d\"), behavior=\"isolated\"),\n", " pre_tokenizers.Split(Regex(r\"[^\\w\\s]\"), behavior=\"isolated\"),\n", " pre_tokenizers.ByteLevel(add_prefix_space=False)\n", "])\n", "\n", "trainer = trainers.BpeTrainer(\n", " vocab_size=VOCAB_SIZE,\n", " min_frequency=3,\n", " special_tokens=[\"\", \"\", \"\", \"\", \"<|user|>\", \"<|assistant|>\"]\n", ")\n", "\n", "if os.path.exists(CORPUS_PATH):\n", " print(\"Training Akshar Tokenizer...\")\n", " tokenizer.train_from_iterator(corpus_iterator(CORPUS_PATH), trainer=trainer)\n", " tokenizer.save(\"data/tokenizer/akshar.json\")\n", " print(\"Success! Tokenizer saved.\")\n", "else:\n", " print(\"Corpus not found. Please run scraper or provide your own data/cleaned/akshar_corpus.jsonl\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. The Rugged Benchmark (N=10,000 Sample)\n", "\n", "This cell recreates the research paper results by comparing Akshar against global and Indic-specific tokenizers." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Loading Benchmarking Tokenizers...\")\n", "models = {\n", " \"Akshar\": (tokenizer, \"akshar\"),\n", " \"Llama-3\": (AutoTokenizer.from_pretrained(\"Xenova/llama-3-tokenizer\"), \"hf\"),\n", " \"GPT-4o\": (tiktoken.encoding_for_model(\"gpt-4o\"), \"tiktoken\"),\n", " \"Sarvam-2B\": (AutoTokenizer.from_pretrained(\"sarvamai/sarvam-2b-v0.5\"), \"hf\")\n", "}\n", "\n", "def calc_f(tok, text, type_name):\n", " words = len(text.split())\n", " if words == 0: return None\n", " if type_name == \"tiktoken\": t = len(tok.encode(text))\n", " elif type_name == \"akshar\": t = len(tok.encode(text).ids)\n", " else: t = len(tok.encode(text))\n", " return t / words\n", "\n", "test_data = list(corpus_iterator(CORPUS_PATH))[:10000]\n", "if test_data:\n", " final_scores = []\n", " for name, (t_obj, t_type) in models.items():\n", " s = [calc_f(t_obj, txt, t_type) for txt in test_data]\n", " avg = sum([x for x in s if x]) / len([x for x in s if x])\n", " final_scores.append({\"Model\": name, \"Fertility\": round(avg, 3)})\n", " \n", " print(pd.DataFrame(final_scores).sort_values(\"Fertility\").to_markdown(index=False))\n", "else:\n", " print(\"Skipping benchmark: No corpus data found.\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12" } }, "nbformat": 4, "nbformat_minor": 4 }