{"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.12"},"kaggle":{"accelerator":"none","dataSources":[],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 1. Installation","metadata":{}},{"cell_type":"code","source":"# Install required packages\n# CRITICAL: Install in this exact order with these exact versions\n\n# Step 1: Core dependencies first\n!pip install -q torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118\n\n# Step 2: PyAnnote ecosystem (latest compatible versions)\n!pip install -q pyannote.core==5.0.0\n!pip install -q pyannote.database==5.1.0  \n!pip install -q pyannote.metrics==3.2.1\n!pip install -q pyannote.audio==3.3.2\n\n# Step 3: Lightning and other dependencies\n!pip install -q pytorch-lightning==2.4.0\n!pip install -q lightning==2.4.0\n!pip install -q tensorboard\n\n# Step 4: Audio processing\n!pip install -q pydub librosa soundfile\n\n# Step 5: Utilities\n!pip install -q pandas tqdm pyyaml\n\n!pip install \"numpy<2.0\"\n\nprint(\"✓ All packages installed successfully!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Imports and Configuration","metadata":{}},{"cell_type":"code","source":"import os\nimport json\nimport yaml\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom typing import Dict, List\nimport warnings\nwarnings.filterwarnings('ignore')\nprint(np.__version__)\n\n# PyTorch\nimport torch\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping\nfrom pytorch_lightning.loggers import TensorBoardLogger\n\n# PyAnnote\nfrom pyannote.audio import Model, Inference, Pipeline\nfrom pyannote.audio.tasks import SpeakerDiarization\nfrom pyannote.database import FileFinder, get_protocol, registry\nfrom pyannote.core import Annotation, Segment, Timeline\nfrom pyannote.metrics.diarization import DiarizationErrorRate\n\n# Audio\nfrom pydub import AudioSegment\nimport torchaudio\n\n# Utils\nfrom tqdm.auto import tqdm\nfrom sklearn.model_selection import train_test_split\n\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:52:55.277901Z","iopub.status.busy":"2026-02-10T09:52:55.277289Z","iopub.status.idle":"2026-02-10T09:53:13.368448Z","shell.execute_reply":"2026-02-10T09:53:13.367825Z","shell.execute_reply.started":"2026-02-10T09:52:55.277873Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== CONFIGURATION ====================\n\n# Hugging Face Token (required for pre-trained models)\nHF_TOKEN = \"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\"  # Replace with your token\n\n# Paths - UPDATE THESE FOR YOUR ENVIRONMENT\nTRAIN_AUDIO_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/train/audio\"  # Directory with .wav files\nTRAIN_ANNOTATION_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/train/annotation\"  # Directory with .csv/.rttm files\nTEST_AUDIO_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/test/audio\"  # Test audio files\n\n# Output paths\nWORK_DIR = \"./work\"\nDATABASE_DIR = f\"{WORK_DIR}/database\"\nMODEL_DIR = f\"{WORK_DIR}/models\"\nCHECKPOINT_DIR = f\"{WORK_DIR}/checkpoints\"\nLOGS_DIR = f\"{WORK_DIR}/logs\"\nSUBMISSION_PATH = f\"{WORK_DIR}/submission.csv\"\n\n# Create directories\nfor d in [WORK_DIR, DATABASE_DIR, MODEL_DIR, CHECKPOINT_DIR, LOGS_DIR]:\n    os.makedirs(d, exist_ok=True)\n\n# ==================== AUDIO SETTINGS ====================\nSAMPLE_RATE = 16000  # Required by pyannote\nMONO = True\n\n# ==================== TRAINING SETTINGS ====================\n# Model configuration\nPRETRAINED_MODEL = \"pyannote/segmentation-3.0\"  # Best segmentation model\nPRETRAINED_EMBEDDING = \"pyannote/wespeaker-voxceleb-resnet34-LM\"  # Best embedding\n\n# Training hyperparameters\nBATCH_SIZE = 32  # Adjust based on GPU memory\nNUM_WORKERS = 4\nMAX_EPOCHS = 20\nLEARNING_RATE = 1e-4\nDURATION = 5.0  # seconds per training chunk\nMAX_SPEAKERS_PER_CHUNK = 3  # Maximum speakers in a chunk\n\n# Early stopping\nPATIENCE = 5\nMIN_DELTA = 0.001\n\n# Train/val split\nVAL_SPLIT = 0.2\n\n# Device\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(f\"✓ Configuration loaded\")\nprint(f\"✓ Device: {DEVICE}\")\nprint(f\"✓ Batch size: {BATCH_SIZE}\")\nprint(f\"✓ Max epochs: {MAX_EPOCHS}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:53:27.204582Z","iopub.status.busy":"2026-02-10T09:53:27.203962Z","iopub.status.idle":"2026-02-10T09:53:27.212091Z","shell.execute_reply":"2026-02-10T09:53:27.211406Z","shell.execute_reply.started":"2026-02-10T09:53:27.204554Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Data Preparation Functions","metadata":{}},{"cell_type":"code","source":"def convert_timestamp_to_seconds(timestamp_str):\n    \"\"\"\n    Convert HH:MM:SS or MM:SS format to seconds.\n    \"\"\"\n    if isinstance(timestamp_str, (int, float)):\n        return float(timestamp_str)\n    \n    timestamp_str = str(timestamp_str).strip()\n    \n    if ':' in timestamp_str:\n        parts = timestamp_str.split(':')\n        if len(parts) == 3:  # HH:MM:SS\n            h, m, s = parts\n            return int(h) * 3600 + int(m) * 60 + float(s)\n        elif len(parts) == 2:  # MM:SS\n            m, s = parts\n            return int(m) * 60 + float(s)\n    \n    return float(timestamp_str)\n\n\ndef load_csv_annotation(csv_path):\n    \"\"\"\n    Load annotation from CSV file.\n    Expected columns: start_time, end_time, speaker_id\n    \"\"\"\n    try:\n        df = pd.read_csv(csv_path)\n        \n        # Normalize column names\n        df.columns = df.columns.str.lower().str.strip()\n        \n        # Create annotation\n        annotation = Annotation()\n        \n        for _, row in df.iterrows():\n            # Handle different column names\n            start_col = [c for c in df.columns if 'start' in c][0]\n            end_col = [c for c in df.columns if 'end' in c][0]\n            speaker_col = [c for c in df.columns if 'speaker' in c or 'id' in c][0]\n            \n            start = convert_timestamp_to_seconds(row[start_col])\n            end = convert_timestamp_to_seconds(row[end_col])\n            speaker_id = row[speaker_col]\n            \n            # Skip invalid segments\n            if pd.isna(start) or pd.isna(end) or pd.isna(speaker_id):\n                continue\n            if start >= end:\n                continue\n            \n            # Format speaker ID\n            speaker = f\"speaker_{int(speaker_id):02d}\"\n            \n            # Add segment\n            annotation[Segment(start, end)] = speaker\n        \n        return annotation\n    \n    except Exception as e:\n        print(f\"Error loading {csv_path}: {e}\")\n        return None\n\n\ndef preprocess_audio(input_path, output_path=None):\n    \"\"\"\n    Preprocess audio to 16kHz mono WAV.\n    \"\"\"\n    if output_path is None:\n        output_path = input_path\n    \n    try:\n        audio = AudioSegment.from_wav(input_path)\n        \n        # Convert to mono\n        if audio.channels > 1:\n            audio = audio.set_channels(1)\n        \n        # Resample to 16kHz\n        if audio.frame_rate != SAMPLE_RATE:\n            audio = audio.set_frame_rate(SAMPLE_RATE)\n        \n        # Export\n        audio.export(output_path, format=\"wav\")\n        return output_path\n    \n    except Exception as e:\n        print(f\"Error preprocessing {input_path}: {e}\")\n        return None\n\nprint(\"✓ Data preparation functions loaded\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:53:31.898469Z","iopub.status.busy":"2026-02-10T09:53:31.898161Z","iopub.status.idle":"2026-02-10T09:53:31.909382Z","shell.execute_reply":"2026-02-10T09:53:31.908581Z","shell.execute_reply.started":"2026-02-10T09:53:31.898441Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Create PyAnnote Database","metadata":{}},{"cell_type":"code","source":"def create_rttm_file(annotation, uri, output_path):\n    \"\"\"\n    Create RTTM file from annotation.\n    RTTM format: SPEAKER <uri> 1 <start> <duration> <NA> <NA> <speaker> <NA> <NA>\n    \"\"\"\n    with open(output_path, 'w') as f:\n        for segment, _, speaker in annotation.itertracks(yield_label=True):\n            line = f\"SPEAKER {uri} 1 {segment.start:.3f} {segment.duration:.3f} <NA> <NA> {speaker} <NA> <NA>\\n\"\n            f.write(line)\n\n\ndef create_uem_file(uri, duration, output_path):\n    \"\"\"\n    Create UEM file (defines evaluation boundaries).\n    UEM format: <uri> NA <start> <end>\n    \"\"\"\n    with open(output_path, 'w') as f:\n        f.write(f\"{uri} NA 0.000 {duration:.3f}\\n\")\n\n\ndef prepare_database():\n    \"\"\"\n    Prepare PyAnnote database structure with train/validation splits.\n    \"\"\"\n    print(\"\\n\" + \"=\"*60)\n    print(\"PREPARING DATABASE\")\n    print(\"=\"*60)\n    \n    # Get all audio files\n    audio_files = sorted(Path(TRAIN_AUDIO_DIR).glob(\"*.wav\"))\n    print(f\"Found {len(audio_files)} audio files\")\n    \n    if len(audio_files) == 0:\n        raise ValueError(f\"No audio files found in {TRAIN_AUDIO_DIR}\")\n    \n    # Load annotations\n    data = []\n    for audio_file in tqdm(audio_files, desc=\"Loading data\"):\n        uri = audio_file.stem\n        \n        # Find corresponding annotation\n        ann_file = Path(TRAIN_ANNOTATION_DIR) / f\"{uri}.csv\"\n        if not ann_file.exists():\n            print(f\"Warning: No annotation for {uri}\")\n            continue\n        \n        # Load annotation\n        annotation = load_csv_annotation(str(ann_file))\n        if annotation is None or len(annotation) == 0:\n            print(f\"Warning: Empty annotation for {uri}\")\n            continue\n        \n        # Get audio duration\n        waveform, sr = torchaudio.load(str(audio_file))\n        duration = waveform.shape[1] / sr\n        \n        data.append({\n            'uri': uri,\n            'audio_path': str(audio_file),\n            'annotation': annotation,\n            'duration': duration\n        })\n    \n    print(f\"Loaded {len(data)} valid samples\")\n    \n    # Split into train/val\n    train_data, val_data = train_test_split(\n        data, \n        test_size=VAL_SPLIT, \n        random_state=42\n    )\n    \n    print(f\"Train: {len(train_data)} samples\")\n    print(f\"Validation: {len(val_data)} samples\")\n    \n    # Create database structure\n    lists_dir = Path(DATABASE_DIR) / \"lists\"\n    annotations_dir = Path(DATABASE_DIR) / \"annotations\"\n    lists_dir.mkdir(parents=True, exist_ok=True)\n    annotations_dir.mkdir(parents=True, exist_ok=True)\n    \n    def create_subset_files(subset_data, subset_name):\n        \"\"\"Create files for a subset (train or development).\"\"\"\n        lst_path = lists_dir / f\"{subset_name}.lst\"\n        rttm_path = annotations_dir / f\"{subset_name}.rttm\"\n        uem_path = annotations_dir / f\"{subset_name}.uem\"\n        \n        # Create .lst file\n        with open(lst_path, 'w') as f:\n            for item in subset_data:\n                f.write(f\"{item['uri']}\\n\")\n        \n        # Create .rttm and .uem files\n        with open(rttm_path, 'w') as rttm_f, open(uem_path, 'w') as uem_f:\n            for item in subset_data:\n                uri = item['uri']\n                annotation = item['annotation']\n                duration = item['duration']\n                \n                # Write RTTM\n                for segment, _, speaker in annotation.itertracks(yield_label=True):\n                    line = f\"SPEAKER {uri} 1 {segment.start:.3f} {segment.duration:.3f} <NA> <NA> {speaker} <NA> <NA>\\n\"\n                    rttm_f.write(line)\n                \n                # Write UEM\n                uem_f.write(f\"{uri} NA 0.000 {duration:.3f}\\n\")\n    \n    create_subset_files(train_data, \"train\")\n    create_subset_files(val_data, \"development\")\n    \n    # Create database.yml\n    database_yml = {\n        \"Databases\": {\n            \"Custom\": f\"{TRAIN_AUDIO_DIR}/{{uri}}.wav\"\n        },\n        \"Protocols\": {\n            \"Custom\": {\n                \"SpeakerDiarization\": {\n                    \"Training\": {\n                        \"train\": {\n                            \"uri\": f\"lists/train.lst\",\n                            \"annotation\": f\"annotations/train.rttm\",\n                            \"annotated\": f\"annotations/train.uem\"\n                        },\n                        \"development\": {\n                            \"uri\": f\"lists/development.lst\",\n                            \"annotation\": f\"annotations/development.rttm\",\n                            \"annotated\": f\"annotations/development.uem\"\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \n    database_yml_path = Path(DATABASE_DIR) / \"database.yml\"\n    with open(database_yml_path, 'w') as f:\n        yaml.dump(database_yml, f, default_flow_style=False)\n    \n    print(f\"\\n✓ Database created at {DATABASE_DIR}\")\n    print(f\"✓ Database config: {database_yml_path}\")\n    \n    return train_data, val_data, str(database_yml_path)\n\n# Create the database\ntrain_data, val_data, database_yml_path = prepare_database()","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:53:35.695137Z","iopub.status.busy":"2026-02-10T09:53:35.694457Z","iopub.status.idle":"2026-02-10T09:53:53.381059Z","shell.execute_reply":"2026-02-10T09:53:53.380371Z","shell.execute_reply.started":"2026-02-10T09:53:35.695103Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Load Protocol","metadata":{}},{"cell_type":"code","source":"# Load database\nregistry.load_database(database_yml_path)\n\n# Get protocol\nprotocol = get_protocol(\n    \"Custom.SpeakerDiarization.Training\",\n    preprocessors={\"audio\": FileFinder()}\n)\n\nprint(\"✓ Protocol loaded successfully\")\n\n# Verify protocol\ntrain_files = list(protocol.train())\ndev_files = list(protocol.development())\n\nprint(f\"\\nProtocol verification:\")\nprint(f\"  Train files: {len(train_files)}\")\nprint(f\"  Development files: {len(dev_files)}\")\n\nif len(train_files) > 0:\n    sample = train_files[0]\n    print(f\"\\nSample file:\")\n    print(f\"  URI: {sample['uri']}\")\n    print(f\"  Audio: {sample['audio']}\")\n    print(f\"  Segments: {len(sample['annotation'])}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:53:53.382821Z","iopub.status.busy":"2026-02-10T09:53:53.382519Z","iopub.status.idle":"2026-02-10T09:53:53.639534Z","shell.execute_reply":"2026-02-10T09:53:53.638829Z","shell.execute_reply.started":"2026-02-10T09:53:53.382796Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Setup Training Task","metadata":{}},{"cell_type":"code","source":"# Create SpeakerDiarization task\ntask = SpeakerDiarization(\n    protocol,\n    duration=DURATION,\n    max_speakers_per_chunk=MAX_SPEAKERS_PER_CHUNK,\n    batch_size=BATCH_SIZE,\n    num_workers=NUM_WORKERS,\n)\n\nprint(\"✓ Training task created\")\nprint(f\"  Duration per chunk: {DURATION}s\")\nprint(f\"  Max speakers per chunk: {MAX_SPEAKERS_PER_CHUNK}\")\nprint(f\"  Batch size: {BATCH_SIZE}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:54:05.483414Z","iopub.status.busy":"2026-02-10T09:54:05.482784Z","iopub.status.idle":"2026-02-10T09:54:05.698749Z","shell.execute_reply":"2026-02-10T09:54:05.698039Z","shell.execute_reply.started":"2026-02-10T09:54:05.483384Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Initialize Model","metadata":{}},{"cell_type":"code","source":"# Option 1: Train from scratch\nmodel = Model.from_pretrained(\n    PRETRAINED_MODEL,\n    use_auth_token=HF_TOKEN,\n    strict=False  # Allow fine-tuning with different number of speakers\n)\n\nmodel.task = task\n# Option 2: Start from a checkpoint (uncomment if resuming)\n# model = Model.from_pretrained(\n#     \"path/to/checkpoint.ckpt\",\n#     strict=False\n# )\n\nprint(\"✓ Model initialized\")\nprint(f\"  Base model: {PRETRAINED_MODEL}\")\nprint(f\"  Parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:54:10.52272Z","iopub.status.busy":"2026-02-10T09:54:10.521964Z","iopub.status.idle":"2026-02-10T09:54:11.554562Z","shell.execute_reply":"2026-02-10T09:54:11.553762Z","shell.execute_reply.started":"2026-02-10T09:54:10.522688Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Setup Training","metadata":{}},{"cell_type":"code","source":"# Callbacks\ncheckpoint_callback = ModelCheckpoint(\n    dirpath=CHECKPOINT_DIR,\n    filename=\"diarization-{epoch:02d}-{val_loss:.4f}\",\n    monitor=\"loss/val\",\n    mode=\"min\",\n    save_top_k=3,\n    save_last=True,\n)\n\nearly_stopping = EarlyStopping(\n    monitor=\"loss/val\",\n    patience=PATIENCE,\n    min_delta=MIN_DELTA,\n    mode=\"min\",\n)\n\n# Logger\nlogger = TensorBoardLogger(\n    save_dir=LOGS_DIR,\n    name=\"diarization_training\"\n)\n\n# Trainer\ntrainer = pl.Trainer(\n    accelerator=\"auto\",\n    devices=1,\n    max_epochs=MAX_EPOCHS,\n    callbacks=[checkpoint_callback, early_stopping],\n    logger=logger,\n    gradient_clip_val=1.0,\n    log_every_n_steps=10,\n)\n\nprint(\"✓ Training setup complete\")\nprint(f\"  Max epochs: {MAX_EPOCHS}\")\nprint(f\"  Early stopping patience: {PATIENCE}\")\nprint(f\"  Checkpoints: {CHECKPOINT_DIR}\")\nprint(f\"  Logs: {LOGS_DIR}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:54:18.921586Z","iopub.status.busy":"2026-02-10T09:54:18.921264Z","iopub.status.idle":"2026-02-10T09:54:18.98127Z","shell.execute_reply":"2026-02-10T09:54:18.980698Z","shell.execute_reply.started":"2026-02-10T09:54:18.921558Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Train Model","metadata":{}},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"STARTING TRAINING\")\nprint(\"=\"*60)\n\n# Train\ntrainer.fit(model)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"TRAINING COMPLETE\")\nprint(\"=\"*60)\nprint(f\"Best checkpoint: {checkpoint_callback.best_model_path}\")\n# print(f\"Best validation loss: {checkpoint_callback.best_model_score:.4f}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T09:54:24.991276Z","iopub.status.busy":"2026-02-10T09:54:24.990962Z","iopub.status.idle":"2026-02-10T10:00:39.367213Z","shell.execute_reply":"2026-02-10T10:00:39.366425Z","shell.execute_reply.started":"2026-02-10T09:54:24.991248Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Evaluate on Validation Set","metadata":{}},{"cell_type":"markdown","source":"## 11. Save Final Model","metadata":{}},{"cell_type":"code","source":"# Save model\nfinal_model_path = Path(MODEL_DIR) / \"final_model\"\nimport shutil\nimport os\n\n# 1. Get the path to the best checkpoint from your trainer/callback\n# (This variable usually exists if you just finished training)\nbest_ckpt_path = checkpoint_callback.best_model_path\n\n# 2. Define where you want to save your final model\n# Pyannote models MUST end in .ckpt to be loaded correctly later\nfinal_path = \"work/models/final_model.ckpt\"\n\n# Ensure directory exists\nos.makedirs(os.path.dirname(final_path), exist_ok=True)\n\n# 3. Copy the file\nprint(f\"Copying best model from: {best_ckpt_path}\")\nshutil.copy(best_ckpt_path, final_path)\n\nprint(f\"✓ Model successfully saved to: {final_path}\")\n\n\n# Save pipeline configuration\npipeline_config = {\n    \"segmentation_model\": str(final_path),\n    \"embedding_model\": PRETRAINED_EMBEDDING,\n    \"parameters\": {\n    \"segmentation\": {\n        \"threshold\": 0.5,\n        \"min_duration_off\": 0.0\n    },\n    \"clustering\": {\n        \"method\": \"centroid\",\n        \"threshold\": 0.7,\n        \"min_cluster_size\": 15\n    }\n}\n}\n\nconfig_path = Path(MODEL_DIR) / \"pipeline_config.json\"\nwith open(config_path, 'w') as f:\n    json.dump(pipeline_config, f, indent=2)\n\nprint(f\"✓ Pipeline config saved to {config_path}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-10T10:01:22.537115Z","iopub.status.busy":"2026-02-10T10:01:22.536621Z","iopub.status.idle":"2026-02-10T10:01:22.556556Z","shell.execute_reply":"2026-02-10T10:01:22.555977Z","shell.execute_reply.started":"2026-02-10T10:01:22.537086Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 15. Upload Model to Hugging Face Hub\n\nUpload your trained model to Hugging Face so you can:\n- Access it from anywhere\n- Share it with others\n- Use it in production\n- Version control your models\n\n**Before running this cell**:\n1. Change `HF_USERNAME` to your Hugging Face username\n2. Optionally change `MODEL_NAME` to your desired model name\n3. Ensure `HF_TOKEN` is set correctly above\n\n**Note**: This will create a public repository. Set `private=True` if you want it private.","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# UPLOAD TRAINED MODEL TO HUGGING FACE HUB\n# ============================================================\n# Add this as a new cell AFTER training completes successfully\n\n# Install required package\n!pip install -q huggingface_hub\n\nimport os\nimport json\nfrom pathlib import Path\nfrom huggingface_hub import HfApi, create_repo, upload_folder\nfrom pyannote.audio import Model\n\nprint(\"=\" * 70)\nprint(\"UPLOADING MODEL TO HUGGING FACE HUB\")\nprint(\"=\" * 70)\n\n# ============================================================\n# CONFIGURATION\n# ============================================================\n\n# Your Hugging Face credentials\nHF_USERNAME = \"zarifmahir21\"  # ← CHANGE THIS to your HF username\nMODEL_NAME = \"bengali-speaker-diarization-v1\"  # ← CHANGE THIS to your desired model name\n\n# Full repository name\nREPO_ID = f\"{HF_USERNAME}/{MODEL_NAME}\"\n\n# Your HF token (already defined in your notebook)\n# HF_TOKEN is already set above\n\nprint(f\"\\nRepository ID: {REPO_ID}\")\nprint(f\"Token available: {'✓' if HF_TOKEN else '✗'}\")\n\n# ============================================================\n# STEP 1: PREPARE MODEL FILES\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 1: Preparing Model Files\")\nprint(\"=\" * 70)\n\n# Create a directory for HF upload\nHF_MODEL_DIR = \"./hf_upload\"\nos.makedirs(HF_MODEL_DIR, exist_ok=True)\n\n# Copy the trained model checkpoint\nimport shutil\n\nsource_model = \"work/models/final_model.ckpt\"\ndest_model = os.path.join(HF_MODEL_DIR, \"pytorch_model.bin\")\n\nif os.path.exists(source_model):\n    print(f\"\\n✓ Copying model from: {source_model}\")\n    shutil.copy(source_model, dest_model)\n    print(f\"✓ Model copied to: {dest_model}\")\nelse:\n    print(f\"✗ Error: Model not found at {source_model}\")\n    raise FileNotFoundError(f\"Model checkpoint not found at {source_model}\")\n\n# ============================================================\n# STEP 2: CREATE MODEL CARD (README.md)\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 2: Creating Model Card\")\nprint(\"=\" * 70)\n\n# Get training metrics if available\ntry:\n    training_der = f\"{der*100:.2f}%\" if 'der' in locals() else \"Not computed\"\n    num_train_files = len(train_data) if 'train_data' in locals() else \"Unknown\"\n    num_val_files = len(val_data) if 'val_data' in locals() else \"Unknown\"\n    num_epochs = trainer.current_epoch if 'trainer' in locals() else \"Unknown\"\nexcept:\n    training_der = \"Not computed\"\n    num_train_files = \"Unknown\"\n    num_val_files = \"Unknown\"\n    num_epochs = \"Unknown\"\n\n# Create comprehensive model card\nmodel_card = f\"\"\"---\nlanguage:\n- bn\ntags:\n- speaker-diarization\n- pyannote\n- pyannote-audio\n- audio\n- voice\n- speech\n- bengali\nlicense: mit\ndatasets:\n- custom\nmetrics:\n- der\nmodel-index:\n- name: {MODEL_NAME}\n  results:\n  - task:\n      type: speaker-diarization\n      name: Speaker Diarization\n    metrics:\n    - type: der\n      value: {training_der}\n      name: Diarization Error Rate\n---\n\n# {MODEL_NAME}\n\nThis is a fine-tuned speaker diarization model based on pyannote.audio, specifically trained on Bengali audio data.\n\n## Model Description\n\nThis model performs speaker diarization - the task of determining \"who spoke when\" in an audio recording. It can:\n- Detect speech segments\n- Identify different speakers\n- Assign timestamps to each speaker's segments\n\n**Language**: Bengali (বাংলা)\n\n**Base Model**: pyannote/segmentation-3.0\n\n## Training Details\n\n### Training Data\n- **Training samples**: {num_train_files}\n- **Validation samples**: {num_val_files}\n- **Audio format**: 16kHz mono WAV\n- **Domain**: Bengali conversational speech\n\n### Training Hyperparameters\n- **Epochs**: {num_epochs}\n- **Batch size**: {BATCH_SIZE if 'BATCH_SIZE' in globals() else 'Unknown'}\n- **Learning rate**: {LEARNING_RATE if 'LEARNING_RATE' in globals() else 'Unknown'}\n- **Duration per chunk**: {DURATION if 'DURATION' in globals() else 'Unknown'}s\n- **Max speakers per chunk**: {MAX_SPEAKERS_PER_CHUNK if 'MAX_SPEAKERS_PER_CHUNK' in globals() else 'Unknown'}\n\n### Performance\n- **Validation DER**: {training_der}\n\n## Usage\n\n### Installation\n\n```bash\npip install pyannote.audio\n```\n\n### Basic Usage\n\n```python\nfrom pyannote.audio import Model\nfrom pyannote.audio.pipelines import SpeakerDiarization\nimport torch\n\n# Load the model\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n\n# Create pipeline\npipeline = SpeakerDiarization(\n    segmentation=model,\n    embedding=\"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    embedding_exclude_overlap=True,\n)\n\n# Set device\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\npipeline.to(DEVICE)\n\n# Instantiate with optimal parameters\npipeline.instantiate({{\n    \"segmentation\": {{\n        \"threshold\": 0.5,\n    }},\n    \"clustering\": {{\n        \"method\": \"centroid\",\n        \"threshold\": 0.7,\n        \"min_cluster_size\": 12,\n    }},\n}})\n\n# Run diarization\ndiarization = pipeline(\"audio.wav\")\n\n# Print results\nfor turn, _, speaker in diarization.itertracks(yield_label=True):\n    print(f\"Speaker {{speaker}}: {{turn.start:.1f}}s - {{turn.end:.1f}}s\")\n```\n\n### Advanced Usage with GPU Optimization\n\n```python\nimport torch\nfrom pyannote.audio import Model\nfrom pyannote.audio.pipelines import SpeakerDiarization\n\n# Setup device\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ntorch.backends.cudnn.benchmark = True  # Enable cuDNN autotuner\n\n# Load model\nmodel = Model.from_pretrained(\"{REPO_ID}\")\nmodel.to(DEVICE)\nmodel.eval()\n\n# Create pipeline\npipeline = SpeakerDiarization(\n    segmentation=model,\n    embedding=\"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    embedding_exclude_overlap=True,\n)\npipeline.to(DEVICE)\n\n# Instantiate\npipeline.instantiate({{\n    \"segmentation\": {{\"threshold\": 0.5}},\n    \"clustering\": {{\"method\": \"centroid\", \"threshold\": 0.7}},\n}})\n\n# Fast inference\nwith torch.no_grad():\n    diarization = pipeline(\"audio.wav\")\n\n# Get speaker segments\nfor segment, _, speaker in diarization.itertracks(yield_label=True):\n    print(f\"[{{segment.start:.2f}}s - {{segment.end:.2f}}s] {{speaker}}\")\n```\n\n## Model Details\n\n### Architecture\n- **Base**: pyannote/segmentation-3.0\n- **Type**: PyanNet segmentation model\n- **Parameters**: ~1.5M\n\n### Input\n- **Format**: WAV audio files\n- **Sample rate**: 16kHz\n- **Channels**: Mono\n\n### Output\n- Speaker diarization annotations (who spoke when)\n- Timestamps in seconds\n- Speaker labels\n\n## Limitations and Bias\n\n- **Language**: Primarily trained on Bengali audio. Performance may degrade on other languages.\n- **Domain**: Optimized for conversational speech. May not perform well on:\n  - Very noisy environments\n  - Music or singing\n  - Overlapping speech (multiple speakers talking simultaneously)\n- **Short utterances**: May struggle with very brief speaker turns (<1 second)\n\n## Ethical Considerations\n\nThis model is intended for research and development purposes. When deploying in production:\n- Ensure compliance with privacy laws and regulations\n- Obtain proper consent for audio recording and processing\n- Be aware of potential biases in speaker identification\n- Do not use for surveillance without proper legal authorization\n\n## Citation\n\nIf you use this model, please cite:\n\n```bibtex\n@misc{{{MODEL_NAME.replace('-', '_')},\n  author = {{{HF_USERNAME}}},\n  title = {{Bengali Speaker Diarization Model}},\n  year = {{2025}},\n  publisher = {{Hugging Face}},\n  howpublished = {{\\\\url{{https://huggingface.co/{REPO_ID}}}}}\n}}\n```\n\nAlso cite the original pyannote.audio paper:\n\n```bibtex\n@inproceedings{{Bredin2020,\n  Title = {{pyannote.audio: neural building blocks for speaker diarization}},\n  Author = {{Herv\\\\'e Bredin and Ruiqing Yin and Juan Manuel Coria and Gregory Gelly and Pavel Korshunov and Marvin Lavechin and Diego Fustes and Hadrien Titeux and Wassim Bouaziz and Marie-Philippe Gill}},\n  Booktitle = {{ICASSP 2020, IEEE International Conference on Acoustics, Speech, and Signal Processing}},\n  Year = {{2020}},\n}}\n```\n\n## License\n\nThis model is released under the MIT License.\n\n## Acknowledgements\n\n- Based on [pyannote.audio](https://github.com/pyannote/pyannote-audio)\n- Pre-trained on [pyannote/segmentation-3.0](https://huggingface.co/pyannote/segmentation-3.0)\n- Embedding model: [pyannote/wespeaker-voxceleb-resnet34-LM](https://huggingface.co/pyannote/wespeaker-voxceleb-resnet34-LM)\n\n## Contact\n\nFor questions and feedback:\n- **Hugging Face**: [{HF_USERNAME}](https://huggingface.co/{HF_USERNAME})\n- **Issues**: [GitHub Issues](https://github.com/{HF_USERNAME}/{MODEL_NAME}/issues) (if applicable)\n\"\"\"\n\n# Save model card\nreadme_path = os.path.join(HF_MODEL_DIR, \"README.md\")\nwith open(readme_path, \"w\", encoding=\"utf-8\") as f:\n    f.write(model_card)\n\nprint(f\"✓ Model card created: {readme_path}\")\n\n# ============================================================\n# STEP 3: CREATE CONFIG FILES\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 3: Creating Configuration Files\")\nprint(\"=\" * 70)\n\n# Create config.yaml (pyannote format)\nconfig_yaml = \"\"\"\n# Model configuration for pyannote.audio\ntask:\n  name: SpeakerDiarization\n  \narchitecture:\n  name: PyanNet\n  \nspecifications:\n  duration: 5.0\n  sample_rate: 16000\n  \ntraining:\n  batch_size: 32\n  learning_rate: 0.0001\n  max_epochs: 20\n\"\"\"\n\nconfig_yaml_path = os.path.join(HF_MODEL_DIR, \"config.yaml\")\nwith open(config_yaml_path, \"w\") as f:\n    f.write(config_yaml)\n\nprint(f\"✓ Config YAML created: {config_yaml_path}\")\n\n# Create pipeline config\npipeline_config = {\n    \"model_type\": \"speaker-diarization\",\n    \"pyannote_version\": \"3.3.2\",\n    \"embedding_model\": \"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    \"optimal_parameters\": {\n        \"segmentation\": {\n            \"threshold\": 0.5,\n            \"min_duration_off\": 0.0\n        },\n        \"clustering\": {\n            \"method\": \"centroid\",\n            \"threshold\": 0.7,\n            \"min_cluster_size\": 12\n        }\n    }\n}\n\npipeline_config_path = os.path.join(HF_MODEL_DIR, \"pipeline_config.json\")\nwith open(pipeline_config_path, \"w\") as f:\n    json.dump(pipeline_config, f, indent=2)\n\nprint(f\"✓ Pipeline config created: {pipeline_config_path}\")\n\n# ============================================================\n# STEP 4: CREATE USAGE EXAMPLE\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 4: Creating Usage Example\")\nprint(\"=\" * 70)\n\nusage_example = f\"\"\"# Example Usage: {MODEL_NAME}\n\nThis example shows how to use the model for speaker diarization.\n\n## Quick Start\n\n```python\nfrom pyannote.audio import Model\nfrom pyannote.audio.pipelines import SpeakerDiarization\nimport torch\n\n# Load model\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n\n# Create pipeline\npipeline = SpeakerDiarization(\n    segmentation=model,\n    embedding=\"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    embedding_exclude_overlap=True,\n)\n\n# Use GPU if available\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\npipeline.to(device)\n\n# Set parameters\npipeline.instantiate({{\n    \"segmentation\": {{\"threshold\": 0.5}},\n    \"clustering\": {{\"method\": \"centroid\", \"threshold\": 0.7}},\n}})\n\n# Run diarization\ndiarization = pipeline(\"your_audio.wav\")\n\n# Print results\nfor turn, _, speaker in diarization.itertracks(yield_label=True):\n    print(f\"[{{turn.start:.1f}}s - {{turn.end:.1f}}s] {{speaker}}\")\n```\n\n## Batch Processing\n\n```python\nfrom pathlib import Path\nfrom tqdm import tqdm\n\naudio_files = list(Path(\"audio_folder\").glob(\"*.wav\"))\n\nresults = {{}}\nwith torch.no_grad():\n    for audio_file in tqdm(audio_files):\n        diarization = pipeline(str(audio_file))\n        results[audio_file.stem] = diarization\n\n# Save results\nfor filename, diarization in results.items():\n    with open(f\"{{filename}}_diarization.txt\", \"w\") as f:\n        for turn, _, speaker in diarization.itertracks(yield_label=True):\n            f.write(f\"{{turn.start:.2f}} {{turn.end:.2f}} {{speaker}}\\\\n\")\n```\n\n## Parameter Tuning\n\n```python\n# Adjust segmentation threshold\n# Lower = more segments (less missed detection, more false alarms)\n# Higher = fewer segments (more missed detection, less false alarms)\n\npipeline.instantiate({{\n    \"segmentation\": {{\"threshold\": 0.4}},  # More sensitive\n    \"clustering\": {{\"threshold\": 0.7}},\n}})\n\n# Adjust clustering threshold  \n# Lower = more speakers (less confusion, more false speakers)\n# Higher = fewer speakers (more confusion, fewer false speakers)\n\npipeline.instantiate({{\n    \"segmentation\": {{\"threshold\": 0.5}},\n    \"clustering\": {{\"threshold\": 0.6}},  # More speakers\n}})\n```\n\"\"\"\n\nexample_path = os.path.join(HF_MODEL_DIR, \"USAGE.md\")\nwith open(example_path, \"w\") as f:\n    f.write(usage_example)\n\nprint(f\"✓ Usage example created: {example_path}\")\n\n# ============================================================\n# STEP 5: UPLOAD TO HUGGING FACE HUB\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 5: Uploading to Hugging Face Hub\")\nprint(\"=\" * 70)\n\n# Verify token\nif not HF_TOKEN or HF_TOKEN == \"your_token_here\":\n    print(\"\\n✗ Error: HF_TOKEN not set!\")\n    print(\"Please set your Hugging Face token in the configuration section above.\")\n    raise ValueError(\"HF_TOKEN not configured\")\n\n# Verify username\nif HF_USERNAME == \"your-username\":\n    print(\"\\n✗ Error: HF_USERNAME not set!\")\n    print(\"Please change HF_USERNAME to your actual Hugging Face username.\")\n    raise ValueError(\"HF_USERNAME not configured\")\n\n# Initialize API\napi = HfApi()\n\ntry:\n    # Create repository\n    print(f\"\\nCreating repository: {REPO_ID}\")\n    create_repo(\n        repo_id=REPO_ID,\n        token=HF_TOKEN,\n        private=False,  # Set to True if you want a private repo\n        exist_ok=True,  # Don't fail if repo already exists\n        repo_type=\"model\"\n    )\n    print(f\"✓ Repository created/verified: https://huggingface.co/{REPO_ID}\")\n    \n    # Upload files\n    print(f\"\\nUploading model files...\")\n    upload_folder(\n        folder_path=HF_MODEL_DIR,\n        repo_id=REPO_ID,\n        token=HF_TOKEN,\n        commit_message=\"Upload fine-tuned Bengali speaker diarization model\",\n    )\n    \n    print(f\"\\n{'='*70}\")\n    print(\"✓ UPLOAD SUCCESSFUL!\")\n    print(f\"{'='*70}\")\n    print(f\"\\n🎉 Your model is now available at:\")\n    print(f\"   https://huggingface.co/{REPO_ID}\")\n    print(f\"\\n📖 To use your model:\")\n    print(f\"\"\"\nfrom pyannote.audio import Model\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n    \"\"\")\n    \nexcept Exception as e:\n    print(f\"\\n✗ Error during upload: {e}\")\n    print(\"\\nTroubleshooting:\")\n    print(\"1. Verify your HF_TOKEN is correct\")\n    print(\"2. Verify your HF_USERNAME is correct\")\n    print(\"3. Check your internet connection\")\n    print(\"4. Ensure you have write permissions\")\n    raise\n\n# ============================================================\n# STEP 6: CREATE DOWNLOAD INSTRUCTIONS\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 6: Generating Download Instructions\")\nprint(\"=\" * 70)\n\ndownload_instructions = f\"\"\"# How to Download and Use Your Model\n\n## From Python\n\n```python\nfrom pyannote.audio import Model\nfrom pyannote.audio.pipelines import SpeakerDiarization\n\n# Download and load model (automatic caching)\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n\n# Create pipeline\npipeline = SpeakerDiarization(\n    segmentation=model,\n    embedding=\"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    embedding_exclude_overlap=True,\n)\n\n# Instantiate and use\npipeline.instantiate({{\n    \"segmentation\": {{\"threshold\": 0.5}},\n    \"clustering\": {{\"method\": \"centroid\", \"threshold\": 0.7}},\n}})\n\n# Run on audio\ndiarization = pipeline(\"audio.wav\")\n```\n\n## From Command Line\n\n```bash\n# Clone repository\ngit clone https://huggingface.co/{REPO_ID}\ncd {MODEL_NAME}\n\n# Use with pyannote\npython -c \"from pyannote.audio import Model; model = Model.from_pretrained('{REPO_ID}'); print('Model loaded successfully!')\"\n```\n\n## Model Location\n\nAfter first download, model is cached at:\n- Linux/Mac: `~/.cache/huggingface/hub/models--{HF_USERNAME}--{MODEL_NAME}`\n- Windows: `C:\\\\Users\\\\<username>\\\\.cache\\\\huggingface\\\\hub\\\\models--{HF_USERNAME}--{MODEL_NAME}`\n\n## Sharing Your Model\n\nShare this link with others:\nhttps://huggingface.co/{REPO_ID}\n\n## API Access\n\n```python\nfrom huggingface_hub import hf_hub_download\n\n# Download specific file\nmodel_file = hf_hub_download(\n    repo_id=\"{REPO_ID}\",\n    filename=\"pytorch_model.bin\"\n)\n```\n\"\"\"\n\ndownload_path = os.path.join(HF_MODEL_DIR, \"DOWNLOAD.md\")\nwith open(download_path, \"w\") as f:\n    f.write(download_instructions)\n\nprint(f\"✓ Download instructions created: {download_path}\")\n\n# ============================================================\n# SUMMARY\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"UPLOAD SUMMARY\")\nprint(\"=\" * 70)\n\nprint(f\"\"\"\n✓ Model repository: {REPO_ID}\n✓ URL: https://huggingface.co/{REPO_ID}\n\nFiles uploaded:\n  - pytorch_model.bin (trained model)\n  - README.md (model card)\n  - config.yaml (model config)\n  - pipeline_config.json (pipeline parameters)\n  - USAGE.md (usage examples)\n  - DOWNLOAD.md (download instructions)\n\nTo use your model anywhere:\n  \n  from pyannote.audio import Model\n  model = Model.from_pretrained(\"{REPO_ID}\")\n\nShare with others:\n  https://huggingface.co/{REPO_ID}\n\"\"\")\n\nprint(\"=\" * 70)\n","metadata":{},"outputs":[],"execution_count":null}]}