{"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"},{"sourceId":14728706,"sourceType":"datasetVersion","datasetId":9411654}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# HF 404 Error Handling","metadata":{}},{"cell_type":"code","source":"!pip install -U transformers accelerate\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:02.928065Z","iopub.execute_input":"2026-02-04T11:35:02.928269Z","iopub.status.idle":"2026-02-04T11:35:06.650417Z","shell.execute_reply.started":"2026-02-04T11:35:02.928248Z","shell.execute_reply":"2026-02-04T11:35:06.649228Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Avoid OOM","metadata":{}},{"cell_type":"code","source":"# Function to clean RAM & vRAM\nimport gc\nimport ctypes\n\ndef cln_memory():\n    gc.collect()\n    ctypes.CDLL(\"libc.so.6\").malloc_trim(0)\n    torch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:06.653075Z","iopub.execute_input":"2026-02-04T11:35:06.653407Z","iopub.status.idle":"2026-02-04T11:35:06.657921Z","shell.execute_reply.started":"2026-02-04T11:35:06.653375Z","shell.execute_reply":"2026-02-04T11:35:06.657254Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Input files","metadata":{}},{"cell_type":"code","source":"import os, glob\nimport pandas as pd\n\nROOT = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition\"\n\nTRAIN_AUDIO_DIR = f\"{ROOT}/transcription/transcription/train/audio\"\nTRAIN_ANNO_DIR  = f\"{ROOT}/transcription/transcription/train/annotation\"\nTEST_AUDIO_DIR  = f\"{ROOT}/transcription/transcription/test/audio\"\nSAMPLE_SUB_PATH = '/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/sample_submission .csv'\n\n# --- sanity checks\nfor p in [TRAIN_AUDIO_DIR, TRAIN_ANNO_DIR, TEST_AUDIO_DIR, SAMPLE_SUB_PATH]:\n    print((\"OK  \" if os.path.exists(p) else \"MISS\"), p)\n\ntrain_wavs = sorted(glob.glob(f\"{TRAIN_AUDIO_DIR}/*.wav\"))\ntrain_txts = sorted(glob.glob(f\"{TRAIN_ANNO_DIR}/*.txt\"))\ntest_wavs  = sorted(glob.glob(f\"{TEST_AUDIO_DIR}/*.wav\"))\n\nprint(\"\\nCounts\")\nprint(\"train wav:\", len(train_wavs))\nprint(\"train txt:\", len(train_txts))\nprint(\"test  wav:\", len(test_wavs))\n\nprint(\"\\nExamples\")\nprint(\"train wav ex:\", os.path.basename(train_wavs[0]) if train_wavs else None)\nprint(\"train txt ex:\", os.path.basename(train_txts[0]) if train_txts else None)\nprint(\"test  wav ex:\", os.path.basename(test_wavs[0])  if test_wavs  else None)\n\nsample_sub = pd.read_csv(SAMPLE_SUB_PATH)\ndisplay(sample_sub.head())\nprint(\"sample_submission columns:\", sample_sub.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:06.659095Z","iopub.execute_input":"2026-02-04T11:35:06.659480Z","iopub.status.idle":"2026-02-04T11:35:06.982033Z","shell.execute_reply.started":"2026-02-04T11:35:06.659455Z","shell.execute_reply":"2026-02-04T11:35:06.981097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Block 2 — BUILD FILE PAIRS (train wav <-> train txt) + TEST LIST\n# =========================\nimport os\nimport pandas as pd\n\ndef stem(p): \n    return os.path.splitext(os.path.basename(p))[0]\n\n# --- Train pairs (expects train_001.wav <-> train_001.txt)\nwav_map = {stem(p): p for p in train_wavs}\ntxt_map = {stem(p): p for p in train_txts}\n\nkeys = sorted(set(wav_map) & set(txt_map))\nmissing_wav = sorted(set(txt_map) - set(wav_map))\nmissing_txt = sorted(set(wav_map) - set(txt_map))\n\nprint(\"Matched pairs:\", len(keys))\nprint(\"Missing wav for txt:\", len(missing_wav), \"| ex:\", missing_wav[:5])\nprint(\"Missing txt for wav:\", len(missing_txt), \"| ex:\", missing_txt[:5])\n\ntrain_pairs = pd.DataFrame({\n    \"id\": keys,\n    \"audio_path\": [wav_map[k] for k in keys],\n    \"txt_path\": [txt_map[k] for k in keys],\n})\n\n# quick peek at transcript length (characters) for sanity\ndef read_txt(p):\n    with open(p, \"r\", encoding=\"utf-8\") as f:\n        return f.read().strip()\n\ntrain_pairs[\"transcript_len\"] = train_pairs[\"txt_path\"].apply(lambda p: len(read_txt(p)))\ndisplay(train_pairs.head())\nprint(\"Transcript length stats:\\n\", train_pairs[\"transcript_len\"].describe())\n\n# --- Test list (we will predict for these)\ntest_map = {stem(p): p for p in test_wavs}\nprint(\"\\nTest files:\", len(test_map), \"| example:\", list(test_map.items())[:2])\n\n# sample_submission tells which filenames to output\nneed_ids = sample_sub[\"filename\"].tolist()\nmissing_test_audio = [i for i in need_ids if i not in test_map]\nprint(\"IDs in sample_submission:\", len(need_ids))\nprint(\"Missing test audio for submission IDs:\", len(missing_test_audio), \"| ex:\", missing_test_audio[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:06.983493Z","iopub.execute_input":"2026-02-04T11:35:06.983818Z","iopub.status.idle":"2026-02-04T11:35:07.166787Z","shell.execute_reply.started":"2026-02-04T11:35:06.983793Z","shell.execute_reply":"2026-02-04T11:35:07.166136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ffmpeg -y -i \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/train/audio/train_001.wav\" -t 300 -ac 1 -ar 16000 /kaggle/working/first5min.wav","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:07.167753Z","iopub.execute_input":"2026-02-04T11:35:07.168539Z","iopub.status.idle":"2026-02-04T11:35:07.481716Z","shell.execute_reply.started":"2026-02-04T11:35:07.168503Z","shell.execute_reply":"2026-02-04T11:35:07.480849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!pip -q install -U faster-whisper","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:07.482896Z","iopub.execute_input":"2026-02-04T11:35:07.483126Z","iopub.status.idle":"2026-02-04T11:35:07.486955Z","shell.execute_reply.started":"2026-02-04T11:35:07.483101Z","shell.execute_reply":"2026-02-04T11:35:07.486292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test(s)\n## Test 1\n(Whisper Turbo Check)","metadata":{}},{"cell_type":"code","source":"# import time, torch\n# from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline\n\n# audio_path = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/train/audio/train_001.wav\"\n# model_id = \"openai/whisper-large-v3-turbo\"\n\n# model = AutoModelForSpeechSeq2Seq.from_pretrained(model_id, torch_dtype=dtype, low_cpu_mem_usage=True)\n# processor = AutoProcessor.from_pretrained(model_id)\n\n# pipe = pipeline(\n#     \"automatic-speech-recognition\",\n#     model=model,\n#     tokenizer=processor.tokenizer,\n#     feature_extractor=processor.feature_extractor,\n#     device=device,\n#     torch_dtype=dtype,\n# )\n\n# # warmup (not timed)\n# _ = pipe(\n#     audio_path,\n#     chunk_length_s=30,\n#     batch_size=16,\n#     return_timestamps=False,\n#     generate_kwargs={\"language\": \"bn\", \"task\": \"transcribe\", \"num_beams\": 1},\n# )\n\n# t0 = time.perf_counter()\n# out = pipe(\n#     audio_path,\n#     chunk_length_s=30,\n#     batch_size=16,\n#     return_timestamps=False,\n#     generate_kwargs={\"language\": \"bn\", \"task\": \"transcribe\", \"num_beams\": 2},\n# )\n# t1 = time.perf_counter()\n\n# elapsed = t1 - t0\n# audio_seconds = 300\n# print(f\"Time: {elapsed:.2f}s | RTF: {elapsed/audio_seconds:.3f} | Speed: {audio_seconds/elapsed:.1f}x realtime\\n\")\n# print(\"--- TRANSCRIPTION ---\\n\")\n# print(out[\"text\"])\n# print(\"\\n--- END ---\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:07.488143Z","iopub.execute_input":"2026-02-04T11:35:07.488349Z","iopub.status.idle":"2026-02-04T11:35:07.500811Z","shell.execute_reply.started":"2026-02-04T11:35:07.488331Z","shell.execute_reply":"2026-02-04T11:35:07.500160Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test 2\n**WER vs Speed vs RTF**\n\n\n*openai/whisper-large-v3-turbo Time: 547.88s | RTF: 0.1038 | Speed: 9.64x realtimeWER:  93.88%<br>mozilla-ai/whisper-large-v3-turbo-bn Time: 528.16s | RTF: 0.1000 | Speed: 9.99x realtimeWER:  78.66%*\n","metadata":{}},{"cell_type":"code","source":"# !pip -q install -U transformers accelerate jiwer\n\n# import re, time, torch\n# from jiwer import wer\n# from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline\n# import librosa\n\n# AUDIO_PATH = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/train/audio/train_001.wav\"\n# REF_PATH   = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/train/annotation/train_001.txt\"\n\n# # -------- Bengali-ish normalization (simple and fair) --------\n# # (Don't do fancy \"remove repeats\" tricks; that'll fake lower WER.)\n# _bn_punct = r\"\"\"[!\"#$%&'()*+,\\-./:;<=>?@\\[\\]^_`{|}~“”‘’।,৷…]+\"\"\"\n# def normalize_bn(s: str) -> str:\n#     s = s.strip()\n#     s = re.sub(_bn_punct, \" \", s)\n#     s = re.sub(r\"\\s+\", \" \", s)\n#     return s.strip()\n\n# # -------- Load reference --------\n# with open(REF_PATH, \"r\", encoding=\"utf-8\") as f:\n#     ref_text = f.read()\n\n# ref_norm = normalize_bn(ref_text)\n\n# # -------- Device / dtype --------\n# device = 0 if torch.cuda.is_available() else -1\n# dtype  = torch.float16 if torch.cuda.is_available() else torch.float32\n\n# # Keep decode settings SAME for fair comparison\n# DECODE = {\n#     \"language\": \"bn\",\n#     \"task\": \"transcribe\",\n#     \"num_beams\": 5,\n#     # Anti-loop knobs (optional but recommended for long audio drift)\n#     \"compression_ratio_threshold\": 2.4,\n#     \"logprob_threshold\": -1.0,\n#     \"no_speech_threshold\": 0.6,\n#     \"temperature\": (0.0, 0.2, 0.4),\n#     \"condition_on_prev_tokens\": False,\n# }\n\n# CHUNK_LEN  = 30\n# BATCH_SIZE = 8  # If OOM: drop to 4 or 2\n\n# MODELS = {\n#     \"openai_turbo\":  \"openai/whisper-large-v3-turbo\",\n#     \"mozilla_bn\":    \"mozilla-ai/whisper-large-v3-turbo-bn\",\n# }\n\n# def transcribe_and_time(model_id: str):\n#     model = AutoModelForSpeechSeq2Seq.from_pretrained(\n#         model_id, torch_dtype=dtype, low_cpu_mem_usage=True, use_safetensors=True\n#     )\n#     proc = AutoProcessor.from_pretrained(model_id)\n\n#     asr = pipeline(\n#         \"automatic-speech-recognition\",\n#         model=model,\n#         tokenizer=proc.tokenizer,\n#         feature_extractor=proc.feature_extractor,\n#         device=device,\n#         torch_dtype=dtype,\n#         ignore_warning=True,\n#     )\n\n#     # Warm-up (not timed)\n#     _ = asr(\n#         AUDIO_PATH,\n#         chunk_length_s=CHUNK_LEN,\n#         batch_size=1,\n#         return_timestamps=False,\n#         generate_kwargs={\"language\":\"bn\",\"task\":\"transcribe\",\"num_beams\":1},\n#     )\n\n#     t0 = time.perf_counter()\n#     out = asr(\n#         AUDIO_PATH,\n#         chunk_length_s=CHUNK_LEN,\n#         batch_size=BATCH_SIZE,\n#         return_timestamps=False,\n#         generate_kwargs=DECODE,\n#     )\n#     t1 = time.perf_counter()\n\n#     hyp = out[\"text\"] if isinstance(out, dict) and \"text\" in out else str(out)\n#     elapsed = t1 - t0\n#     return hyp, elapsed\n\n# results = {}\n# for name, mid in MODELS.items():\n#     hyp_text, elapsed = transcribe_and_time(mid)\n#     hyp_norm = normalize_bn(hyp_text)\n\n#     w = wer(ref_norm, hyp_norm)\n#     audio_seconds = librosa.get_duration(path= AUDIO_PATH)\n#     rtf = elapsed / audio_seconds\n#     speed = audio_seconds / elapsed\n\n#     results[name] = {\n#         \"model\": mid,\n#         \"time_s\": elapsed,\n#         \"rtf\": rtf,\n#         \"speed_x\": speed,\n#         \"wer\": w,\n#         \"hyp\": hyp_text,\n#     }\n\n# # -------- Print summary --------\n# print(\"\\n===== SUMMARY =====\")\n# for k, v in results.items():\n#     print(f\"\\n[{k}] {v['model']}\")\n#     print(f\"Time: {v['time_s']:.2f}s | RTF: {v['rtf']:.3f} | Speed: {v['speed_x']:.1f}x realtime\")\n#     print(f\"WER:  {v['wer']*100:.2f}%\")\n\n# # -------- Print transcripts --------\n# print(\"\\n\\n===== TRANSCRIPTS =====\")\n# for k, v in results.items():\n#     print(\"\\n\" + \"=\"*80)\n#     print(f\"{k} -> {v['model']}\")\n#     print(v[\"hyp\"])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:07.503163Z","iopub.execute_input":"2026-02-04T11:35:07.503410Z","iopub.status.idle":"2026-02-04T11:35:07.516875Z","shell.execute_reply.started":"2026-02-04T11:35:07.503387Z","shell.execute_reply":"2026-02-04T11:35:07.516110Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 1: VAD Segmentation","metadata":{}},{"cell_type":"code","source":"!pip -q install -U silero-vad soundfile librosa","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:07.518119Z","iopub.execute_input":"2026-02-04T11:35:07.518400Z","iopub.status.idle":"2026-02-04T11:35:11.029232Z","shell.execute_reply.started":"2026-02-04T11:35:07.518373Z","shell.execute_reply":"2026-02-04T11:35:11.028350Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!find \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription/train/audio\" \\\n  -type f -name \"*.wav\" -size 0 -print | head","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:11.030549Z","iopub.execute_input":"2026-02-04T11:35:11.030818Z","iopub.status.idle":"2026-02-04T11:35:11.293390Z","shell.execute_reply.started":"2026-02-04T11:35:11.030790Z","shell.execute_reply":"2026-02-04T11:35:11.292692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, json, gc\nfrom pathlib import Path\n\nimport torch\nimport numpy as np\nimport soundfile as sf\nimport librosa\n\nfrom silero_vad import load_silero_vad, get_speech_timestamps\n\n# ---- set your dataset root ----\nROOT = \"/kaggle/input/dl-sprint-4-0-bengali-long-form-speech-recognition/transcription/transcription\"\nTRAIN_AUDIO_DIR = f\"{ROOT}/train/audio\"\nTRAIN_TXT_DIR   = f\"{ROOT}/train/annotation\"\n\nOUT_SEG_DIR = \"/kaggle/working/vad_segments/train\"\nOUT_MANIFEST = \"/kaggle/working/vad_train_manifest.jsonl\"\n\nos.makedirs(OUT_SEG_DIR, exist_ok=True)\n\n# # ---- VAD parameters ----\n# SR = 16000\n# MAX_SEG_S = 30.0         # hard max segment length\n# SPEECH_PAD_MS = 200      # pad around detected speech\n# MIN_SPEECH_MS = 250      # ignore tiny speech blips\n# MIN_KEEP_S = 0.5         # drop segments shorter than this\n\n# vad_model = load_silero_vad()\n\n# def load_audio_mono16k(path: str):\n#     wav, sr = sf.read(path)\n#     if wav.ndim == 2:\n#         wav = wav.mean(axis=1)\n#     if sr != SR:\n#         wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=SR)\n#     return wav.astype(np.float32)\n\n# def vad_segments(path: str):\n#     wav = load_audio_mono16k(path)\n#     wav_t = torch.from_numpy(wav)\n\n#     speech_ts = get_speech_timestamps(\n#         wav_t,\n#         vad_model,\n#         sampling_rate=SR,\n#         min_speech_duration_ms=MIN_SPEECH_MS,\n#         speech_pad_ms=SPEECH_PAD_MS,\n#     )\n#     if not speech_ts:\n#         return [], wav\n\n#     spans = [(ts[\"start\"]/SR, ts[\"end\"]/SR) for ts in speech_ts]\n#     return spans, wav\n\n# def write_segments(orig_wav_path: str, spans, wav_np: np.ndarray, out_dir: str):\n#     base = Path(orig_wav_path).stem\n#     seg_rows = []\n#     seg_idx = 0\n\n#     for (s0, e0) in spans:\n#         cur = s0\n#         while cur < e0:\n#             end = min(cur + MAX_SEG_S, e0)\n#             a = int(cur * SR)\n#             b = int(end * SR)\n#             seg = wav_np[a:b]\n\n#             dur = (b - a) / SR\n#             if dur < MIN_KEEP_S:\n#                 cur = end\n#                 continue\n\n#             out_path = f\"{out_dir}/{base}__{seg_idx:05d}.wav\"\n#             sf.write(out_path, seg, SR)\n\n#             seg_rows.append({\n#                 \"orig_id\": base,           # train_001\n#                 \"orig_wav\": orig_wav_path,\n#                 \"seg_wav\": out_path,\n#                 \"start_s\": round(cur, 3),\n#                 \"end_s\": round(end, 3),\n#                 \"dur_s\": round(dur, 3),\n#             })\n\n#             seg_idx += 1\n#             cur = end\n\n#     return seg_rows\n\n# # ---- main loop ----\n# train_wavs = sorted(glob.glob(f\"{TRAIN_AUDIO_DIR}/*.wav\"))\n# print(\"Found train wavs:\", len(train_wavs))\n# print(\"Example:\", train_wavs[:3])\n\n# total = 0\n# with open(OUT_MANIFEST, \"w\", encoding=\"utf-8\") as f:\n#     for wav_path in train_wavs:\n#         base = Path(wav_path).stem                 # train_001\n#         # skip 0-byte file\n#         if os.path.getsize(wav_path) == 0:\n#             print(f\"[SKIP] {base} (0 bytes)\")\n#             continue\n#         txt_path = f\"{TRAIN_TXT_DIR}/{base}.txt\"   # train_001.txt\n\n#         spans, wav_np = vad_segments(wav_path)\n#         rows = write_segments(wav_path, spans, wav_np, OUT_SEG_DIR)\n\n#         # attach transcript path (full transcript per file)\n#         for r in rows:\n#             r[\"txt_path\"] = txt_path\n#             f.write(json.dumps(r, ensure_ascii=False) + \"\\n\")\n\n#         total += len(rows)\n#         print(f\"{base}: spans={len(spans)} -> segments={len(rows)}\")\n\n#         # free memory each file\n#         del wav_np\n#         gc.collect()\n\n# print(\"\\nDONE\")\n# print(\"Segments saved to:\", OUT_SEG_DIR)\n# print(\"Manifest saved to:\", OUT_MANIFEST)\n# print(\"Total segments:\", total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:11.294656Z","iopub.execute_input":"2026-02-04T11:35:11.295240Z","iopub.status.idle":"2026-02-04T11:35:13.091978Z","shell.execute_reply.started":"2026-02-04T11:35:11.295212Z","shell.execute_reply":"2026-02-04T11:35:13.090911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Phase 2: pseudo-label segments using model.generate (NO pipeline) =====\n\nimport os, json, time\nimport numpy as np\nimport soundfile as sf\nimport librosa\nimport torch\nfrom transformers import AutoModelForSpeechSeq2Seq, AutoProcessor\n\nDATA_ROOT = \"/kaggle/input/vad-long-bengaliasr\"\nMANIFEST_IN = f\"{DATA_ROOT}/vad_train_manifest.jsonl\"\nSEG_DIR = f\"{DATA_ROOT}/vad_segments/train\"\nOUT_LABELED = \"/kaggle/working/train_labeled_manifest.jsonl\"\n\nassert os.path.exists(MANIFEST_IN), \"Missing manifest\"\nassert os.path.isdir(SEG_DIR), \"Missing segments dir\"\n\nMODEL_ID = \"openai/whisper-large-v3-turbo\"\nLANG = \"bn\"\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\ndtype  = torch.float16 if torch.cuda.is_available() else torch.float32\nprint(\"CUDA:\", torch.cuda.is_available(), \"| device:\", device, \"| dtype:\", dtype)\n\nmodel = AutoModelForSpeechSeq2Seq.from_pretrained(\n    MODEL_ID, torch_dtype=dtype, low_cpu_mem_usage=True, use_safetensors=True\n).to(device)\nprocessor = AutoProcessor.from_pretrained(MODEL_ID)\n\n# Whisper prompt tokens for bn transcription\nforced_decoder_ids = processor.get_decoder_prompt_ids(language=LANG, task=\"transcribe\")\n\nGEN = dict(\n    forced_decoder_ids=forced_decoder_ids,\n    num_beams=1,\n    condition_on_prev_tokens=False,\n)\n\ndef resolve_seg_path(seg_wav_value: str) -> str:\n    # map by filename into SEG_DIR\n    if seg_wav_value and os.path.exists(seg_wav_value):\n        return seg_wav_value\n    return os.path.join(SEG_DIR, os.path.basename(seg_wav_value))\n\ndef load_audio_16k(path: str):\n    wav, sr = sf.read(path)\n    if wav.ndim == 2:\n        wav = wav.mean(axis=1)\n    if sr != 16000:\n        wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=16000)\n    return wav.astype(np.float32)\n\n# resume\ndone = set()\nif os.path.exists(OUT_LABELED):\n    with open(OUT_LABELED, \"r\", encoding=\"utf-8\") as f:\n        for line in f:\n            try:\n                done.add(json.loads(line)[\"audio_path\"])\n            except:\n                pass\n    print(\"Resume: already labeled:\", len(done))\n\nt0 = time.time()\nMAX_TO_LABEL = 2_000\nseen = written = missing = 0\n\nwith open(MANIFEST_IN, \"r\", encoding=\"utf-8\") as fin, open(OUT_LABELED, \"a\", encoding=\"utf-8\") as fout:\n    for line in fin:\n        seen += 1\n        item = json.loads(line)\n\n        seg_path = resolve_seg_path(item[\"seg_wav\"])\n        if not os.path.exists(seg_path):\n            missing += 1\n            continue\n        if seg_path in done:\n            continue\n\n        audio = load_audio_16k(seg_path)\n\n        inputs = processor(audio, sampling_rate=16000, return_tensors=\"pt\")\n        input_features = inputs[\"input_features\"].to(device, dtype=dtype)\n\n        with torch.no_grad():\n            pred_ids = model.generate(input_features, **GEN)\n\n        text = processor.batch_decode(pred_ids, skip_special_tokens=True)[0].strip()\n\n        if len(text) < 2:\n            continue\n\n        rec = {\n            \"audio_path\": seg_path,\n            \"text\": text,\n            \"orig_id\": item.get(\"orig_id\"),\n            \"start_s\": item.get(\"start_s\"),\n            \"end_s\": item.get(\"end_s\"),\n            \"dur_s\": item.get(\"dur_s\"),\n        }\n        fout.write(json.dumps(rec, ensure_ascii=False) + \"\\n\")\n\n        done.add(seg_path)\n        written += 1\n\n        if written % 200 == 0:\n            dt = time.time() - t0\n            print(f\"labeled={written} | seen={seen} | missing={missing} | {dt/60:.1f} min\")\n        if written >= MAX_TO_LABEL:\n            print(f\"[STOP] Reached MAX_TO_LABEL={MAX_TO_LABEL}\")\n            break\n\ndt = time.time() - t0\nprint(\"\\nPHASE 2 DONE\")\nprint(\"Output:\", OUT_LABELED)\nprint(\"Labeled:\", written, \"| Seen:\", seen, \"| Missing:\", missing)\nprint(f\"Time: {dt/60:.1f} minutes\")\n\nprint(\"\\n--- Preview (first 3 lines) ---\")\nwith open(OUT_LABELED, \"r\", encoding=\"utf-8\") as f:\n    for _ in range(3):\n        print(f.readline().strip())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T11:35:13.093209Z","iopub.execute_input":"2026-02-04T11:35:13.093849Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Libraries","metadata":{}},{"cell_type":"code","source":"# from torch.utils.data import Dataset\n# from transformers import (\n#     AutoModelForSpeechSeq2Seq,\n#     AutoProcessor,\n#     Seq2SeqTrainingArguments,\n#     Seq2SeqTrainer,\n# )\n# from peft import LoraConfig, get_peft_model, TaskType","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# LABELED_JSONL = \"/kaggle/working/train_labeled_manifest.jsonl\"  # from Phase 2\n# assert os.path.exists(LABELED_JSONL), \"Missing labeled manifest. Run Phase 2 first.\"\n\n# OUT_ADAPTER_DIR = \"/kaggle/working/lora_shard_1\"\n# os.makedirs(OUT_ADAPTER_DIR, exist_ok=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"# BASE_MODEL_ID = \"mozilla-ai/whisper-large-v3-turbo-bn\"\n# LANG = \"bn\"\n\n# device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n# dtype  = torch.float16 if torch.cuda.is_available() else torch.float32\n# print(\"CUDA:\", torch.cuda.is_available(), \"| device:\", device, \"| dtype:\", dtype)\n\n# processor = AutoProcessor.from_pretrained(BASE_MODEL_ID)\n\n# model = AutoModelForSpeechSeq2Seq.from_pretrained(\n#     BASE_MODEL_ID,\n#     torch_dtype=dtype,\n#     low_cpu_mem_usage=True,\n#     use_safetensors=True\n# ).to(device)\n\n# # Important for training stability / memory\n# model.config.use_cache = False\n\n# # Optional: keep language/task behavior consistent\n# model.generation_config.language = LANG\n# model.generation_config.task = \"transcribe\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LoRA","metadata":{}},{"cell_type":"code","source":"# lora_cfg = LoraConfig(\n#     task_type=TaskType.SEQ_2_SEQ_LM,\n#     r=16,\n#     lora_alpha=32,\n#     lora_dropout=0.05,\n#     bias=\"none\",\n#     target_modules=[\"q_proj\", \"v_proj\"],\n# )\n# model = get_peft_model(model, lora_cfg)\n# model.print_trainable_parameters()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# records = []\n# with open(LABELED_JSONL, \"r\", encoding=\"utf-8\") as f:\n#     for line in f:\n#         obj = json.loads(line)\n#         # safety: skip any train_089 segment if it exists\n#         if os.path.basename(obj[\"audio_path\"]).startswith(\"train_089__\"):\n#             continue\n#         # safety: skip missing/0-byte\n#         if (not os.path.exists(obj[\"audio_path\"])) or (os.path.getsize(obj[\"audio_path\"]) == 0):\n#             continue\n#         # safety: skip empty text\n#         if len(obj.get(\"text\",\"\").strip()) < 2:\n#             continue\n#         records.append({\"audio_path\": obj[\"audio_path\"], \"text\": obj[\"text\"].strip()})\n\n# print(\"Usable labeled segments:\", len(records))\n\n# # ---------- shuffle + split into 5 shards ----------\n# seed = 42\n# random.seed(seed)\n# random.shuffle(records)\n\n# shards = [records[i::5] for i in range(5)]\n# print(\"Shard sizes:\", [len(s) for s in shards])\n\n# shard_id = 0  # <-- shard 1/5 (0-based)\n# train_records = shards[shard_id]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class AudioTextDataset(Dataset):\n#     def __init__(self, items):\n#         self.items = items\n#     def __len__(self):\n#         return len(self.items)\n#     def __getitem__(self, idx):\n#         return self.items[idx]\n\n# def load_audio_16k(path: str):\n#     wav, sr = sf.read(path)\n#     if wav.ndim == 2:\n#         wav = wav.mean(axis=1)\n#     if sr != 16000:\n#         wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=16000)\n#     return wav.astype(np.float32)\n\n# class WhisperCollator:\n#     def __init__(self, processor, max_label_len=256):\n#         self.processor = processor\n#         self.max_label_len = max_label_len\n\n#     def __call__(self, batch):\n#         audios = [load_audio_16k(b[\"audio_path\"]) for b in batch]\n#         texts  = [b[\"text\"] for b in batch]\n\n#         feats = self.processor.feature_extractor(\n#             audios, sampling_rate=16000, return_tensors=\"pt\"\n#         )\n\n#         with self.processor.as_target_processor():\n#             labels = self.processor.tokenizer(\n#                 texts,\n#                 return_tensors=\"pt\",\n#                 padding=True,\n#                 truncation=True,\n#                 max_length=self.max_label_len,\n#             ).input_ids\n\n#         labels[labels == self.processor.tokenizer.pad_token_id] = -100\n\n#         return {\n#             \"input_features\": feats[\"input_features\"],\n#             \"labels\": labels\n#         }\n\n# train_ds = AudioTextDataset(train_records)\n# collator = WhisperCollator(processor)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # ---------- training args (Kaggle-friendly) ----------\n# args = Seq2SeqTrainingArguments(\n#     output_dir=\"/kaggle/working/tmp_train_shard1\",\n#     per_device_train_batch_size=2,\n#     gradient_accumulation_steps=16,     # effective batch=16\n#     learning_rate=1e-4,\n#     warmup_steps=200,\n#     num_train_epochs=1,\n#     fp16=torch.cuda.is_available(),\n#     logging_steps=25,\n#     save_steps=500,\n#     save_total_limit=1,\n#     report_to=\"none\",\n#     dataloader_num_workers=2,\n#     optim=\"adamw_torch\",\n# )","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# trainer = Seq2SeqTrainer(\n#     model=model,\n#     args=args,\n#     train_dataset=train_ds,\n#     data_collator=collator,\n#     tokenizer=processor.tokenizer,\n# )\n\n# trainer.train()\n\n# # ---------- save adapter (small) ----------\n# model.save_pretrained(OUT_ADAPTER_DIR)\n# processor.save_pretrained(OUT_ADAPTER_DIR)\n\n# print(\"Saved LoRA adapter to:\", OUT_ADAPTER_DIR)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Shard 2","metadata":{}},{"cell_type":"code","source":"# # ===== Phase 4: Continue LoRA fine-tune on shard 2/5 and save adapter =====\n# !pip -q install -U peft accelerate transformers soundfile librosa\n\n# import os, json, random\n# import numpy as np\n# import torch\n# import soundfile as sf\n# import librosa\n\n# from torch.utils.data import Dataset\n# from transformers import (\n#     AutoModelForSpeechSeq2Seq,\n#     AutoProcessor,\n#     Seq2SeqTrainingArguments,\n#     Seq2SeqTrainer,\n# )\n# from peft import PeftModel\n\n# # ---------- paths ----------\n# LABELED_JSONL = \"/kaggle/working/train_labeled_manifest.jsonl\"\n# ADAPTER_IN    = \"/kaggle/working/lora_shard_1\"     # previous adapter\n# OUT_ADAPTER_DIR = \"/kaggle/working/lora_shard_2\"   # new adapter\n\n# assert os.path.exists(LABELED_JSONL), \"Missing labeled manifest.\"\n# assert os.path.isdir(ADAPTER_IN), \"Missing lora_shard_1 adapter folder.\"\n# os.makedirs(OUT_ADAPTER_DIR, exist_ok=True)\n\n# # ---------- model ----------\n# BASE_MODEL_ID = \"mozilla-ai/whisper-large-v3-turbo-bn\"\n# LANG = \"bn\"\n\n# device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n# dtype  = torch.float16 if torch.cuda.is_available() else torch.float32\n# print(\"CUDA:\", torch.cuda.is_available(), \"| device:\", device, \"| dtype:\", dtype)\n\n# processor = AutoProcessor.from_pretrained(BASE_MODEL_ID)\n\n# base = AutoModelForSpeechSeq2Seq.from_pretrained(\n#     BASE_MODEL_ID,\n#     torch_dtype=dtype,\n#     low_cpu_mem_usage=True,\n#     use_safetensors=True\n# ).to(device)\n\n# base.config.use_cache = False\n# base.generation_config.language = LANG\n# base.generation_config.task = \"transcribe\"\n\n# # load LoRA weights from shard 1\n# model = PeftModel.from_pretrained(base, ADAPTER_IN)\n# model.print_trainable_parameters()\n\n# # ---------- load labeled records ----------\n# records = []\n# with open(LABELED_JSONL, \"r\", encoding=\"utf-8\") as f:\n#     for line in f:\n#         obj = json.loads(line)\n#         p = obj[\"audio_path\"]\n#         if os.path.basename(p).startswith(\"train_089__\"):\n#             continue\n#         if (not os.path.exists(p)) or (os.path.getsize(p) == 0):\n#             continue\n#         t = obj.get(\"text\",\"\").strip()\n#         if len(t) < 2:\n#             continue\n#         records.append({\"audio_path\": p, \"text\": t})\n\n# print(\"Usable labeled segments:\", len(records))\n\n# # ---------- same shuffle/split logic as Phase 3 ----------\n# seed = 42\n# random.seed(seed)\n# random.shuffle(records)\n# shards = [records[i::5] for i in range(5)]\n# print(\"Shard sizes:\", [len(s) for s in shards])\n\n# shard_id = 1  # <-- shard 2/5\n# train_records = shards[shard_id]\n\n# # ---------- dataset + collator ----------\n# class AudioTextDataset(Dataset):\n#     def __init__(self, items): self.items = items\n#     def __len__(self): return len(self.items)\n#     def __getitem__(self, idx): return self.items[idx]\n\n# def load_audio_16k(path: str):\n#     wav, sr = sf.read(path)\n#     if wav.ndim == 2:\n#         wav = wav.mean(axis=1)\n#     if sr != 16000:\n#         wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=16000)\n#     return wav.astype(np.float32)\n\n# class WhisperCollator:\n#     def __init__(self, processor, max_label_len=256):\n#         self.processor = processor\n#         self.max_label_len = max_label_len\n\n#     def __call__(self, batch):\n#         audios = [load_audio_16k(b[\"audio_path\"]) for b in batch]\n#         texts  = [b[\"text\"] for b in batch]\n\n#         feats = self.processor.feature_extractor(audios, sampling_rate=16000, return_tensors=\"pt\")\n\n#         with self.processor.as_target_processor():\n#             labels = self.processor.tokenizer(\n#                 texts,\n#                 return_tensors=\"pt\",\n#                 padding=True,\n#                 truncation=True,\n#                 max_length=self.max_label_len,\n#             ).input_ids\n\n#         labels[labels == self.processor.tokenizer.pad_token_id] = -100\n#         return {\"input_features\": feats[\"input_features\"], \"labels\": labels}\n\n# train_ds = AudioTextDataset(train_records)\n# collator = WhisperCollator(processor)\n\n# # ---------- training args ----------\n# args = Seq2SeqTrainingArguments(\n#     output_dir=\"/kaggle/working/tmp_train_shard2\",\n#     per_device_train_batch_size=2,\n#     gradient_accumulation_steps=8,\n#     learning_rate=1e-4,\n#     warmup_steps=200,\n#     num_train_epochs=1,\n#     fp16=torch.cuda.is_available(),\n#     logging_steps=25,\n#     save_steps=500,\n#     save_total_limit=1,\n#     report_to=\"none\",\n#     dataloader_num_workers=2,\n#     optim=\"adamw_torch\",\n# )\n\n# trainer = Seq2SeqTrainer(\n#     model=model,\n#     args=args,\n#     train_dataset=train_ds,\n#     data_collator=collator,\n#     tokenizer=processor.tokenizer,\n# )\n\n# trainer.train()\n\n# # ---------- save updated adapter ----------\n# model.save_pretrained(OUT_ADAPTER_DIR)\n# processor.save_pretrained(OUT_ADAPTER_DIR)\n\n# print(\"Saved LoRA adapter to:\", OUT_ADAPTER_DIR)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Shard 3","metadata":{}},{"cell_type":"code","source":"# # ===== Phase 5: Continue LoRA fine-tune on shard 3/5 and save adapter =====\n# !pip -q install -U peft accelerate transformers soundfile librosa\n\n# import os, json, random\n# import numpy as np\n# import torch\n# import soundfile as sf\n# import librosa\n\n# from torch.utils.data import Dataset\n# from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, Seq2SeqTrainingArguments, Seq2SeqTrainer\n# from peft import PeftModel\n\n# # ---------- paths ----------\n# LABELED_JSONL   = \"/kaggle/working/train_labeled_manifest.jsonl\"\n# ADAPTER_IN      = \"/kaggle/working/lora_shard_2\"     # from previous shard\n# OUT_ADAPTER_DIR = \"/kaggle/working/lora_shard_3\"     # new output\n\n# assert os.path.exists(LABELED_JSONL), \"Missing labeled manifest.\"\n# assert os.path.isdir(ADAPTER_IN), \"Missing lora_shard_2 adapter folder.\"\n# os.makedirs(OUT_ADAPTER_DIR, exist_ok=True)\n\n# # ---------- model ----------\n# BASE_MODEL_ID = \"mozilla-ai/whisper-large-v3-turbo-bn\"\n# LANG = \"bn\"\n\n# device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n# dtype  = torch.float16 if torch.cuda.is_available() else torch.float32\n# print(\"CUDA:\", torch.cuda.is_available(), \"| device:\", device, \"| dtype:\", dtype)\n\n# processor = AutoProcessor.from_pretrained(BASE_MODEL_ID)\n\n# base = AutoModelForSpeechSeq2Seq.from_pretrained(\n#     BASE_MODEL_ID, torch_dtype=dtype, low_cpu_mem_usage=True, use_safetensors=True\n# ).to(device)\n\n# base.config.use_cache = False\n# base.generation_config.language = LANG\n# base.generation_config.task = \"transcribe\"\n\n# # Load LoRA from shard 2\n# model = PeftModel.from_pretrained(base, ADAPTER_IN)\n# model.print_trainable_parameters()\n\n# # ---------- load labeled records ----------\n# records = []\n# with open(LABELED_JSONL, \"r\", encoding=\"utf-8\") as f:\n#     for line in f:\n#         obj = json.loads(line)\n#         p = obj[\"audio_path\"]\n#         t = obj.get(\"text\", \"\").strip()\n\n#         # safety skips\n#         if os.path.basename(p).startswith(\"train_089__\"):\n#             continue\n#         if (not os.path.exists(p)) or (os.path.getsize(p) == 0):\n#             continue\n#         if len(t) < 2:\n#             continue\n\n#         records.append({\"audio_path\": p, \"text\": t})\n\n# print(\"Usable labeled segments:\", len(records))\n\n# # ---------- IMPORTANT: same shuffle seed as before ----------\n# seed = 42\n# random.seed(seed)\n# random.shuffle(records)\n\n# # Split into 5 shards (same as earlier)\n# shards = [records[i::5] for i in range(5)]\n# print(\"Shard sizes:\", [len(s) for s in shards])\n\n# shard_id = 2  # <-- shard 3/5 (0-based)\n# train_records = shards[shard_id]\n# print(\"Training on shard_id=2 count:\", len(train_records))\n\n# # ---------- dataset + collator ----------\n# class AudioTextDataset(Dataset):\n#     def __init__(self, items): self.items = items\n#     def __len__(self): return len(self.items)\n#     def __getitem__(self, idx): return self.items[idx]\n\n# def load_audio_16k(path: str):\n#     wav, sr = sf.read(path)\n#     if wav.ndim == 2:\n#         wav = wav.mean(axis=1)\n#     if sr != 16000:\n#         wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=16000)\n#     return wav.astype(np.float32)\n\n# class WhisperCollator:\n#     def __init__(self, processor, max_label_len=256):\n#         self.processor = processor\n#         self.max_label_len = max_label_len\n\n#     def __call__(self, batch):\n#         audios = [load_audio_16k(b[\"audio_path\"]) for b in batch]\n#         texts  = [b[\"text\"] for b in batch]\n\n#         feats = self.processor.feature_extractor(audios, sampling_rate=16000, return_tensors=\"pt\")\n\n#         with self.processor.as_target_processor():\n#             labels = self.processor.tokenizer(\n#                 texts, return_tensors=\"pt\", padding=True, truncation=True, max_length=self.max_label_len\n#             ).input_ids\n\n#         labels[labels == self.processor.tokenizer.pad_token_id] = -100\n#         return {\"input_features\": feats[\"input_features\"], \"labels\": labels}\n\n# train_ds = AudioTextDataset(train_records)\n# collator = WhisperCollator(processor)\n\n# # ---------- training args ----------\n# args = Seq2SeqTrainingArguments(\n#     output_dir=\"/kaggle/working/tmp_train_shard3\",\n#     per_device_train_batch_size=2,\n#     gradient_accumulation_steps=8,  # effective batch=16\n#     learning_rate=1e-4,\n#     warmup_steps=200,\n#     num_train_epochs=1,\n#     fp16=torch.cuda.is_available(),\n#     logging_steps=25,\n#     save_steps=500,\n#     save_total_limit=1,\n#     report_to=\"none\",\n#     dataloader_num_workers=2,\n#     optim=\"adamw_torch\",\n# )\n\n# trainer = Seq2SeqTrainer(\n#     model=model,\n#     args=args,\n#     train_dataset=train_ds,\n#     data_collator=collator,\n#     tokenizer=processor.tokenizer,\n# )\n\n# trainer.train()\n\n# # ---------- save adapter ----------\n# model.save_pretrained(OUT_ADAPTER_DIR)\n# processor.save_pretrained(OUT_ADAPTER_DIR)\n# print(\"Saved LoRA adapter to:\", OUT_ADAPTER_DIR)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}