{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":129276,"databundleVersionId":15506988,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q -U openai-whisper transformers torch accelerate bitsandbytes librosa pydub silero-vad","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-05T07:30:32.648580Z","iopub.execute_input":"2026-02-05T07:30:32.649002Z","iopub.status.idle":"2026-02-05T07:30:58.835613Z","shell.execute_reply.started":"2026-02-05T07:30:32.648976Z","shell.execute_reply":"2026-02-05T07:30:58.834731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport torch\nimport whisper\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nfrom transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n\n# Set device\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-05T07:30:58.836880Z","iopub.execute_input":"2026-02-05T07:30:58.837529Z","iopub.status.idle":"2026-02-05T07:31:17.369409Z","shell.execute_reply.started":"2026-02-05T07:30:58.837498Z","shell.execute_reply":"2026-02-05T07:31:17.368701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------------------------------------------------------\n# 2. MODEL LOADING (MEMORY OPTIMIZED)\n# -----------------------------------------------------------------------------\n\n# A. Load VAD Model (Silero) for Silence Removal\n# This boosts RTF by ignoring non-speech parts\nvad_model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',\n                                  model='silero_vad',\n                                  force_reload=False,\n                                  trust_repo=True)\n(get_speech_timestamps, save_audio, read_audio, VADIterator, collect_chunks) = utils\n\n\n# B. Load Whisper (ASR)\n# 'turbo' is much faster than 'large-v3' with similar accuracy, essential for RTF score\nprint(\"Loading Whisper-Turbo...\")\nasr_model = whisper.load_model(\"turbo\", device=device)\n\n\n# C. Load TigerLLM (Refiner) with 4-bit Quantization\n# We use the 1B Instruct version. It's small enough to fit in memory alongside Whisper.\n# Using a larger model (e.g., 7B) would likely cause OOM on Kaggle.\nprint(\"Loading TigerLLM-1B...\")\nllm_id = \"md-nishat-008/TigerLLM-1B-it\" \n\nbnb_config = BitsAndBytesConfig(\n    load_in_4bit=True,\n    bnb_4bit_quant_type=\"nf4\",\n    bnb_4bit_use_double_quant=True,\n    bnb_4bit_compute_dtype=torch.float16\n)\n\ntokenizer = AutoTokenizer.from_pretrained(llm_id)\nllm_model = AutoModelForCausalLM.from_pretrained(\n    llm_id, \n    quantization_config=bnb_config, \n    device_map=\"auto\" # Automatically maps to GPU\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-05T07:31:17.370350Z","iopub.execute_input":"2026-02-05T07:31:17.370911Z","iopub.status.idle":"2026-02-05T07:32:16.921502Z","shell.execute_reply.started":"2026-02-05T07:31:17.370878Z","shell.execute_reply":"2026-02-05T07:32:16.920728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------------------------------------------------------\n# 3. HELPER FUNCTIONS\n# -----------------------------------------------------------------------------\n\ndef clear_memory():\n    \"\"\"Aggressive memory cleanup to prevent OOM\"\"\"\n    gc.collect()\n    torch.cuda.empty_cache()\n\ndef correct_transcription(raw_text):\n    \"\"\"\n    Refines ASR output using TigerLLM. \n    The prompt is enhanced to focus on grammar without adding hallucinations.\n    \"\"\"\n    if not raw_text or len(raw_text) < 5: \n        return raw_text\n\n    # Enhanced Prompt:\n    # 1. Sets context (Bangla grammar correction).\n    # 2. Provides the input.\n    # 3. Enforces output format to avoid conversational filler.\n    prompt = (\n        \"নিচের অগোছালো বাংলা বাক্যটি ব্যাকরণগতভাবে শুদ্ধ করে লেখ। কোনো নতুন তথ্য যোগ করবে না।\\n\\n\"\n        f\"অশুদ্ধ বাক্য: {raw_text}\\n\"\n        \"শুদ্ধ বাক্য:\"\n    )\n\n    inputs = tokenizer(prompt, return_tensors=\"pt\").to(device)\n    \n    with torch.no_grad():\n        output_tokens = llm_model.generate(\n            **inputs,\n            max_new_tokens=len(inputs.input_ids[0]) + 100, # Limit generation length\n            do_sample=False,  # Deterministic (faster & more stable)\n            temperature=0.0,\n            repetition_penalty=1.1 # Prevents repeating loops\n        )\n\n    decoded = tokenizer.decode(output_tokens[0], skip_special_tokens=True)\n    \n    # Robust post-processing to extract just the answer\n    if \"শুদ্ধ বাক্য:\" in decoded:\n        cleaned = decoded.split(\"শুদ্ধ বাক্য:\")[-1].strip()\n    else:\n        cleaned = decoded.strip()\n        \n    return cleaned\n\ndef transcribe_pipeline(file_path):\n    \"\"\"\n    Full pipeline: VAD -> Whisper -> LLM Refinement\n    \"\"\"\n    try:\n        # Step 1: VAD - Pre-process audio to get speech timestamps\n        wav = read_audio(file_path, sampling_rate=16000)\n        speech_timestamps = get_speech_timestamps(wav, vad_model, sampling_rate=16000)\n        \n        # Merge speech chunks into a single tensor for Whisper\n        # If no speech detected, return empty string\n        if len(speech_timestamps) == 0:\n            return \"\"\n            \n        speech_wav = collect_chunks(speech_timestamps, wav)\n        \n        # We need to save this temporary \"speech-only\" audio or pass the tensor directly.\n        # Whisper expects a path or a numpy array. We'll pass the numpy array.\n        # speech_wav is a Tensor, convert to numpy\n        audio_np = speech_wav.numpy()\n\n        # Step 2: Whisper Transcription\n        # We use fp16=True for speed\n        result = asr_model.transcribe(\n            audio_np, \n            language=\"bn\",\n            fp16=True,\n            beam_size=1  # Greedy decoding is faster\n        )\n        raw_text = result['text'].strip()\n\n        # Step 3: LLM Refinement\n        # Only refine if the text is long enough to warrant it (saves time)\n        final_text = correct_transcription(raw_text)\n        \n        return final_text\n\n    except Exception as e:\n        print(f\"Error processing {file_path}: {e}\")\n        return \"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-05T07:32:16.923396Z","iopub.execute_input":"2026-02-05T07:32:16.923984Z","iopub.status.idle":"2026-02-05T07:32:16.933624Z","shell.execute_reply.started":"2026-02-05T07:32:16.923955Z","shell.execute_reply":"2026-02-05T07:32:16.933036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------------------------------------------------------\n# 4. MAIN EXECUTION LOOP\n# -----------------------------------------------------------------------------\n\n# Define paths\ntest_audio_dir = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/test/audio\"\nsubmission_file = \"submission.csv\"\n\n# Get file list\ntest_files = [f for f in os.listdir(test_audio_dir) if f.endswith('.wav')]\nresults = []\n\nprint(f\"Found {len(test_files)} files to process.\")\n\nfor i, filename in enumerate(tqdm(test_files)):\n    file_path = os.path.join(test_audio_dir, filename)\n    \n    # Run pipeline\n    transcript = transcribe_pipeline(file_path)\n    \n    # Store result (Competition format: 'id', 'sentence')\n    file_id = filename  # Use full filename or remove extension based on sample_submission\n    results.append({\"id\": file_id, \"sentence\": transcript})\n    \n    # Aggressive cleanup every 10 files to keep memory stable\n    if i % 10 == 0:\n        clear_memory()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-05T07:32:16.934417Z","iopub.execute_input":"2026-02-05T07:32:16.934680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------------------------------------------------------\n# 5. SUBMISSION\n# -----------------------------------------------------------------------------\ndf = pd.DataFrame(results)\n\n# Ensure ID format matches sample_submission (usually just filename or id)\n# If the sample submission expects IDs without .wav, uncomment below:\n# df['id'] = df['id'].str.replace('.wav', '', regex=False)\n\ndf.to_csv(submission_file, index=False)\nprint(\"Submission file saved successfully!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}