{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.0"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":129271,"databundleVersionId":15506307},{"sourceType":"datasetVersion","sourceId":14909803,"datasetId":9540194,"databundleVersionId":15775364}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# v69 — ResNet293 + SimAMResNet100 Embedding Swap (v58 base)","metadata":{}},{"cell_type":"code","source":"import os, sys, torch\n_orig_load = torch.load\ndef _patched_load(*a, **kw):\n    kw['weights_only'] = False\n    return _orig_load(*a, **kw)\ntorch.load = _patched_load\n\nimport torchaudio\nif not hasattr(torchaudio, 'list_audio_backends'):\n    torchaudio.list_audio_backends = lambda: [\"soundfile\"]\n\nget_ipython().system('pip install -q \"numpy==2.0.2\"')\n# Install onnxruntime FIRST (CPU version — onnxruntime-gpu fails silently on Kaggle)\n# Must be installed BEFORE pyannote so ONNX_IS_AVAILABLE is True at import\nget_ipython().system('pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null; pip install -q onnxruntime')\nget_ipython().system('pip install -q --no-deps \"diarizers @ git+https://github.com/huggingface/diarizers.git\"')\nget_ipython().system('pip install -q \"transformers>=4.40.0\" \"accelerate>=0.14.0\" \"datasets[audio]>=2.11.0,<4.0.0\" audiomentations denoiser')\nget_ipython().system('pip install -q \"pyannote.audio<4.0\"')\nget_ipython().system('pip install -q speechbrain huggingface_hub soundfile tqdm scikit-learn')\n\nimport onnxruntime as ort\nprint(f\"onnxruntime={ort.__version__}, providers={ort.get_available_providers()}\")\nimport numpy as np\nassert np.__version__ == \"2.0.2\"\nprint(f\"numpy={np.__version__}, torch={torch.__version__}, CUDA={torch.cuda.is_available()}\")\nprint(f\"GPU: {torch.cuda.get_device_name(0)}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, json, time, copy, gc, warnings\nimport numpy as np, pandas as pd, torch, torchaudio, soundfile as sf\nfrom pathlib import Path\nfrom pyannote.core import Annotation, Segment\nfrom pyannote.metrics.diarization import DiarizationErrorRate\nfrom pyannote.audio import Pipeline as PyannotePipeline\nfrom pyannote.audio import Audio\nfrom pyannote.audio.pipelines.speaker_verification import (\n    PretrainedSpeakerEmbedding,\n    ONNXWeSpeakerPretrainedSpeakerEmbedding,\n)\nwarnings.filterwarnings('ignore')\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nDATA_ROOT = Path(\"/kaggle/input/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization\")\nTRAIN_AUDIO = DATA_ROOT / \"train\" / \"audio\"\nTRAIN_ANNOT = DATA_ROOT / \"train\" / \"annotation\"\nif not TRAIN_ANNOT.exists(): TRAIN_ANNOT = DATA_ROOT / \"train\" / \"annotations\"\nTEST_AUDIO = DATA_ROOT / \"test\" / \"audio\"\nOUT_PATH = Path(\"/kaggle/working/submission.csv\")\nSIMAM_ONNX = Path(\"/kaggle/input/wespeaker-voxblink2-samresnet100/speaker-embedding.onnx\")\n\ntry:\n    from kaggle_secrets import UserSecretsClient\n    HF_TOKEN = UserSecretsClient().get_secret(\"HUGGINGFACE_TOKEN\")\nexcept:\n    HF_TOKEN = \"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\"\nfrom huggingface_hub import login\nlogin(token=HF_TOKEN)\n\ntrain_wavs = sorted(TRAIN_AUDIO.glob(\"*.wav\"))\ntrain_csvs = sorted(TRAIN_ANNOT.glob(\"*.csv\"))\ncsv_map = {p.stem: p for p in train_csvs}\nTRAIN_FILES = [(w, csv_map[w.stem]) for w in train_wavs if w.stem in csv_map]\nprint(f\"Train: {len(TRAIN_FILES)} pairs, Test: {len(sorted(TEST_AUDIO.glob('*.wav')))} files\")\nprint(f\"SimAM ONNX exists: {SIMAM_ONNX.exists()}\")\n\ndef _hhmmss(t):\n    p = str(t).strip().split(\":\")\n    if len(p) == 3: return int(p[0])*3600 + int(p[1])*60 + float(p[2])\n    if len(p) == 2: return int(p[0])*60 + float(p[1])\n    return float(t)\n\ndef load_ref(csv_path, uri=\"x\"):\n    df = pd.read_csv(csv_path)\n    df.columns = [c.lower().strip() for c in df.columns]\n    df = df.dropna(subset=[\"start_time\", \"end_time\", \"speaker_id\"])\n    ann = Annotation(uri=uri)\n    for _, r in df.iterrows():\n        s, e = _hhmmss(r[\"start_time\"]), _hhmmss(r[\"end_time\"])\n        if e > s: ann[Segment(s, e)] = f\"SPEAKER_{int(r['speaker_id'])}\"\n    return ann\n\ndef annotation_to_list(ann):\n    lmap, nxt = {}, 1\n    out = []\n    for turn, _, speaker in ann.itertracks(yield_label=True):\n        if speaker not in lmap: lmap[speaker] = f\"SPEAKER_{nxt}\"; nxt += 1\n        out.append({\"start_time\": round(turn.start, 2), \"end_time\": round(turn.end, 2), \"speaker_id\": lmap[speaker]})\n    return out\n\ndef evaluate_annotation(ref, hyp):\n    m = DiarizationErrorRate(collar=0.25)\n    der = m(ref, hyp)\n    c = m.accumulated_\n    return {\"DER\": float(der), \"FA\": float(c[\"false alarm\"]), \"Miss\": float(c[\"missed detection\"]),\n            \"Conf\": float(c[\"confusion\"]), \"Total\": float(c[\"total\"])}\n\nrefs = {}\nfor wav_path, csv_path in TRAIN_FILES:\n    refs[wav_path.stem] = load_ref(csv_path, wav_path.stem)\nprint(f\"[OK] {len(refs)} refs loaded\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pipeline = PyannotePipeline.from_pretrained(\"pyannote/speaker-diarization-3.1\", use_auth_token=HF_TOKEN)\npipeline.to(DEVICE)\nBASE_PARAMS = pipeline.parameters(instantiated=True)\nprint(f\"Default params: {BASE_PARAMS}\")\n\nfrom diarizers import SegmentationModel as DiarSegModel\nseg_model = DiarSegModel().from_pretrained(\n    \"lucius-40/speaker-segmentation-fine-tuned-bengali\"\n).to_pyannote_model()\npipeline._segmentation.model = seg_model.to(DEVICE)\nprint(\"[OK] fine-tuned-bengali seg swapped (v58 config)\")\n\nORIG_EMBEDDING = pipeline._embedding\nORIG_AUDIO = pipeline._audio\nprint(f\"Original embedding: {type(ORIG_EMBEDDING).__name__}, dim={ORIG_EMBEDDING.dimension}\")\n# Baseline already confirmed: DER=0.2446, FA=5.5%, Miss=4.2%, Conf=14.8%","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\" * 70)\nprint(\"Loading embedding models\")\nprint(\"=\" * 70)\n\nEMB_MODELS = {}\n\n# 1. ResNet293-LM (4x bigger than default resnet34, 0.447% EER vs 0.605%)\n# pyannote routes \"wespeaker\" strings to ONNX — uses speaker-embedding.onnx from HF repo\ntry:\n    emb_293 = PretrainedSpeakerEmbedding(\"eek/wespeaker-voxceleb-resnet293-LM\", device=DEVICE)\n    EMB_MODELS[\"resnet293\"] = emb_293\n    print(f\"[OK] ResNet293-LM: dim={emb_293.dimension}, sr={emb_293.sample_rate}\")\nexcept Exception as e:\n    print(f\"[FAIL] ResNet293-LM: {e}\")\n\n# 2. SimAMResNet100 (VoxBlink2, 0.202% EER, multilingual, 256d)\nif SIMAM_ONNX.exists():\n    try:\n        emb_simam = ONNXWeSpeakerPretrainedSpeakerEmbedding(str(SIMAM_ONNX), device=DEVICE)\n        EMB_MODELS[\"simam100\"] = emb_simam\n        print(f\"[OK] SimAMResNet100: dim={emb_simam.dimension}, sr={emb_simam.sample_rate}\")\n    except Exception as e:\n        print(f\"[FAIL] SimAMResNet100: {e}\")\nelse:\n    print(f\"[SKIP] SimAMResNet100: ONNX not found at {SIMAM_ONNX}\")\n\nprint(f\"\\n{len(EMB_MODELS)} embedding models loaded: {list(EMB_MODELS.keys())}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"QUICK_FILES = TRAIN_FILES[:3]\nQUICK_STEMS = [w.stem for w, _ in QUICK_FILES]\nprint(f\"Quick sweep files: {QUICK_STEMS}\")\n\nTHRESHOLDS = [0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80]\nBASELINE_DER = 0.2446  # confirmed in v88\n\nall_results = {}\n\nfor emb_name, emb_model in EMB_MODELS.items():\n    print(f\"\\n{'='*70}\")\n    print(f\"--- {emb_name} ---\")\n    print(f\"{'='*70}\")\n\n    pipeline._embedding = emb_model\n    pipeline._audio = Audio(sample_rate=emb_model.sample_rate, mono=\"downmix\")\n\n    # Quick sweep on 3 files\n    quick_results = {}\n    for thresh in THRESHOLDS:\n        params = copy.deepcopy(BASE_PARAMS)\n        params[\"clustering\"][\"threshold\"] = thresh\n        pipeline.instantiate(params)\n\n        tfa, tmiss, tconf, tref = 0, 0, 0, 0\n        t0 = time.time()\n        for wav_path, csv_path in QUICK_FILES:\n            stem = wav_path.stem\n            hyp = pipeline(str(wav_path), min_speakers=8, max_speakers=25)\n            r = evaluate_annotation(refs[stem], hyp)\n            tfa += r[\"FA\"]; tmiss += r[\"Miss\"]; tconf += r[\"Conf\"]; tref += r[\"Total\"]\n\n        dt = time.time() - t0\n        der = (tfa + tmiss + tconf) / tref\n        quick_results[thresh] = der\n        print(f\"  t={thresh:.2f}: DER={der:.4f} FA={tfa/tref*100:.1f}% \"\n              f\"Miss={tmiss/tref*100:.1f}% Conf={tconf/tref*100:.1f}% [{dt:.0f}s]\")\n\n    best_thresh = min(quick_results, key=quick_results.get)\n    print(f\"  >> Best quick: t={best_thresh:.2f} DER={quick_results[best_thresh]:.4f}\")\n\n    # Full eval at best threshold + neighbors\n    full_thresholds = sorted(set([\n        max(0.3, best_thresh - 0.05),\n        best_thresh,\n        min(1.0, best_thresh + 0.05),\n    ]))\n\n    print(f\"\\n  Full eval on all 10 files at t={full_thresholds}...\")\n    for thresh in full_thresholds:\n        params = copy.deepcopy(BASE_PARAMS)\n        params[\"clustering\"][\"threshold\"] = thresh\n        pipeline.instantiate(params)\n\n        tfa, tmiss, tconf, tref = 0, 0, 0, 0\n        hyps = {}\n        t0 = time.time()\n        for wav_path, csv_path in TRAIN_FILES:\n            stem = wav_path.stem\n            hyp = pipeline(str(wav_path), min_speakers=8, max_speakers=25)\n            hyp.uri = stem\n            hyps[stem] = hyp\n            r = evaluate_annotation(refs[stem], hyp)\n            tfa += r[\"FA\"]; tmiss += r[\"Miss\"]; tconf += r[\"Conf\"]; tref += r[\"Total\"]\n            print(f\"    {stem}: DER={r['DER']:.4f} spk={len(hyp.labels())}\")\n\n        dt = time.time() - t0\n        der = (tfa + tmiss + tconf) / tref\n        name = f\"{emb_name}_t{thresh:.2f}\"\n        all_results[name] = {\n            \"DER\": der, \"FA%\": tfa/tref*100, \"Miss%\": tmiss/tref*100,\n            \"Conf%\": tconf/tref*100, \"emb_name\": emb_name, \"threshold\": thresh, \"hyps\": hyps,\n        }\n        print(f\"\\n  FULL {name}: DER={der:.4f} FA={tfa/tref*100:.1f}% \"\n              f\"Miss={tmiss/tref*100:.1f}% Conf={tconf/tref*100:.1f}% [{dt:.0f}s]\")\n\n    gc.collect(); torch.cuda.empty_cache()\n\n# Summary\nsorted_r = sorted(all_results.items(), key=lambda x: x[1][\"DER\"])\nprint(f\"\\n{'='*70}\")\nprint(\"SUMMARY\")\nprint(f\"{'='*70}\")\nprint(f\"  baseline_resnet34: DER={BASELINE_DER:.4f} (v58 reference)\")\nfor name, r in sorted_r:\n    vs = f\" ({(r['DER']-BASELINE_DER)*100:+.2f}pp vs baseline)\"\n    marker = \" <<<< BEST\" if name == sorted_r[0][0] else \"\"\n    print(f\"  {name}: DER={r['DER']:.4f} Conf={r['Conf%']:.1f}%{vs}{marker}\")\n\nBEST_NAME = sorted_r[0][0]\nBEST = sorted_r[0][1]\nprint(f\"\\n>>> BEST: {BEST_NAME} DER={BEST['DER']:.4f}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"{'='*70}\")\nprint(f\"TEST PREDICTIONS — {BEST_NAME}\")\nprint(f\"{'='*70}\")\n\nemb_name = BEST[\"emb_name\"]\npipeline._embedding = EMB_MODELS[emb_name]\npipeline._audio = Audio(sample_rate=EMB_MODELS[emb_name].sample_rate, mono=\"downmix\")\n\nparams = copy.deepcopy(BASE_PARAMS)\nparams[\"clustering\"][\"threshold\"] = BEST[\"threshold\"]\npipeline.instantiate(params)\n\ntest_wavs = sorted(TEST_AUDIO.glob(\"*.wav\"))\ntotal_time, total_dur = 0, 0\nrows = []\n\nfor wp in test_wavs:\n    t0 = time.time()\n    stem = wp.stem\n    hyp = pipeline(str(wp), min_speakers=8, max_speakers=25)\n    hyp.uri = stem\n    dt = time.time() - t0\n    dur = sf.info(str(wp)).duration\n    total_time += dt; total_dur += dur\n    diar_list = annotation_to_list(hyp)\n    rows.append(dict(filename=stem, diarization=json.dumps(diar_list, ensure_ascii=False)))\n    print(f\"  {stem}: {len(diar_list)} segs, {len(hyp.labels())} spk, {dt:.1f}s\")\n\nsub = pd.DataFrame(rows)\nsub.to_csv(str(OUT_PATH), index=False)\nprint(f\"\\nSaved {OUT_PATH} — {len(rows)} files, RTF={total_time/total_dur:.4f}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"\\n{'='*70}\")\nprint(\"BASELINE TEST PREDICTIONS (v58 = resnet34 + default)\")\nprint(f\"{'='*70}\")\n\npipeline._embedding = ORIG_EMBEDDING\npipeline._audio = ORIG_AUDIO\nparams = copy.deepcopy(BASE_PARAMS)\npipeline.instantiate(params)\n\nrows_bl = []\nfor wp in sorted(TEST_AUDIO.glob(\"*.wav\")):\n    stem = wp.stem\n    hyp = pipeline(str(wp), min_speakers=8, max_speakers=25)\n    hyp.uri = stem\n    diar_list = annotation_to_list(hyp)\n    rows_bl.append(dict(filename=stem, diarization=json.dumps(diar_list, ensure_ascii=False)))\n    print(f\"  {stem}: {len(diar_list)} segs, {len(hyp.labels())} spk\")\n\nbl_path = Path(\"/kaggle/working/submission_baseline.csv\")\npd.DataFrame(rows_bl).to_csv(str(bl_path), index=False)\nprint(f\"\\nBaseline saved: {bl_path}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import subprocess\nsub = pd.read_csv(str(OUT_PATH))\nassert len(sub) == 14\n\nmsg = f\"v69: {BEST_NAME} DER={BEST['DER']:.4f}\"\nprint(f\"Submitting: {msg}\")\nresult = subprocess.run(\n    [\"kaggle\", \"competitions\", \"submit\", \"-c\", \"dl-sprint-4-0-bengali-speaker-diarization-challenge\",\n     \"-f\", str(OUT_PATH), \"-m\", msg], capture_output=True, text=True)\nprint(result.stdout or result.stderr)\n\nprint(f\"\\nv69 DONE\")\nprint(f\"  Baseline resnet34: DER={BASELINE_DER:.4f}\")\nprint(f\"  Best: {BEST_NAME} DER={BEST['DER']:.4f}\")","metadata":{},"outputs":[],"execution_count":null}]}