jefffffff9 Claude Sonnet 4.6 commited on
Commit
aef60bc
·
1 Parent(s): 8e41441

Proactive notebook audit: fix 5 issues before they cause errors

Browse files

1. Cell 3: remove dead LORA_R/ALPHA/DROPOUT/TARGETS vars (PEFT removed)
2. Cell 13: fix train_test_split test_size -- was float(0.05) which gives
0 eval samples on small datasets; now max(1, int(0.05 * n))
3. Cell 22: remove forced_decoder_ids kwarg from model.generate() --
transformers 5.x removed this param; config already has it set
4. Cell 13: remove num_proc=2 from waxal map (multiprocessing + audio can
hang on Kaggle when dataset is small)
5. Update stale markdown cells (LoRA/PEFT/8-bit references removed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

notebooks/kaggle_master_trainer.ipynb CHANGED
@@ -62,7 +62,7 @@
62
  "metadata": {},
63
  "outputs": [],
64
  "source": [
65
- "# ── Cell 3: CONFIGURATION — edit these before each run ───────────────────────\nimport os\n\n# ─── Language to train ───────────────────────────────────────────────────────\n# 'bam' = Bambara 'ful' = Fula\nTRAIN_LANG = 'bam'\n\n# ─── Model ───────────────────────────────────────────────────────────────────\nWHISPER_MODEL_ID = 'openai/whisper-small'\nTARGET_SR = 16_000\n\n# ─── HuggingFace repos ───────────────────────────────────────────────────────\nHF_USERNAME = 'ous-sow'\nFEEDBACK_REPO_ID = f'{HF_USERNAME}/sahel-agri-feedback'\nADAPTER_REPO_ID = f'{HF_USERNAME}/sahel-agri-adapters'\n\n# ─── LoRA hyper-parameters ───────────────────────────────────────────────────\nLORA_R = 32 # rank — higher = more capacity, more VRAM\nLORA_ALPHA = 64 # scaling factor (typically 2× r)\nLORA_DROPOUT = 0.05\n# Regex targets encoder self-attention only -- avoids PEFT/transformers 5.x\n# TypeError (input_ids passed twice) that occurs when LoRA wraps decoder layers.\nLORA_TARGETS = r'model\\.encoder\\.layers\\.\\d+\\.self_attn\\.(q|v)_proj'\n\n# ─── Training hyper-parameters ───────────────────────────────────────────────\nMAX_STEPS = 4_000 # T4 ~45 min; set 8000 for a deeper run\nBATCH_SIZE = 16\nGRAD_ACCUM = 2 # effective batch = 32\nLEARNING_RATE = 1e-3\nWARMUP_STEPS = 200\nSAVE_STEPS = 500\nEVAL_STEPS = 500\nLOGGING_STEPS = 50\nMAX_WAXAL_TRAIN = 5_000 # cap WaxalNLP samples (streaming budget)\nCORRECTION_REPEAT= 3 # upsample user corrections Nx for emphasis\n\n# ─── Paths (Kaggle working dir) ───────────────────────────────────────────────\nWORKING_DIR = '/kaggle/working'\nOUTPUT_DIR = f'{WORKING_DIR}/adapter_{TRAIN_LANG}'\nDATA_DIR = f'{WORKING_DIR}/data'\nAUDIO_DIR = f'{WORKING_DIR}/audio_feedback'\n\nLANG_NAME = {'bam': 'bambara', 'ful': 'fula'}.get(TRAIN_LANG, TRAIN_LANG)\n\nprint(f'Language : {TRAIN_LANG} ({LANG_NAME})')\nprint(f'Model : {WHISPER_MODEL_ID}')\nprint(f'Output : {OUTPUT_DIR}')\nprint(f'Max steps : {MAX_STEPS}')"
66
  ]
67
  },
68
  {
@@ -141,7 +141,9 @@
141
  "id": "cell-merge",
142
  "metadata": {},
143
  "outputs": [],
144
- "source": "# -- Cell 12: Build & merge all datasets --------------------------------------\nfrom datasets import Dataset, Audio as HFAudio, concatenate_datasets\nfrom functools import partial\n\ntrain_ds = None # set here so Cell 12b can detect whether we succeeded\neval_ds = None\nall_parts = []\n\n# -- Part A: User corrections -------------------------------------------------\nif correction_records:\n print(f'Part A: {len(correction_records)} corrections x {CORRECTION_REPEAT}')\n rec_list = correction_records * CORRECTION_REPEAT\n corr_ds = Dataset.from_dict({\n 'audio': [r['local_audio'] for r in rec_list],\n 'transcription': [r.get('corrected_text') or r.get('transcription', '') for r in rec_list],\n })\n corr_ds = corr_ds.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n corr_ds = corr_ds.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=corr_ds.column_names,\n )\n all_parts.append(corr_ds)\n print(f' -> {len(corr_ds)} samples')\nelse:\n print('Part A: no corrections -- skipping')\n\n# -- Part B: WaxalNLP ---------------------------------------------------------\nif waxal_ds is not None:\n print(f'Part B: materialising up to {MAX_WAXAL_TRAIN} WaxalNLP samples ...')\n waxal_rows = list(waxal_ds.take(MAX_WAXAL_TRAIN))\n waxal_local = Dataset.from_dict({\n 'audio': [r['audio'] for r in waxal_rows],\n 'transcription': [r.get(WAXAL_TEXT_COL, '') for r in waxal_rows],\n })\n waxal_local = waxal_local.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n waxal_local = waxal_local.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=waxal_local.column_names,\n num_proc=2,\n )\n all_parts.append(waxal_local)\n print(f' -> {len(waxal_local)} samples')\nelse:\n print('Part B: WaxalNLP not available -- skipping')\n\n# -- Part C: External datasets ------------------------------------------------\nfor ext_ds, text_col in external_datasets:\n ext_rows = list(ext_ds)\n if not ext_rows:\n continue\n ext_local = Dataset.from_dict({\n 'audio': [r['audio'] for r in ext_rows],\n 'transcription': [r.get(text_col, '') for r in ext_rows],\n })\n ext_local = ext_local.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n ext_local = ext_local.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=ext_local.column_names,\n num_proc=2,\n )\n all_parts.append(ext_local)\n print(f'Part C: {len(ext_local)} external samples')\n\n# -- Result -------------------------------------------------------------------\nprint(f'\\nData summary: {len(all_parts)} source(s) loaded')\n\nif not all_parts:\n print('No real data available for Bambara yet.')\n print('Cell 12b (below) will build a synthetic dataset so the full')\n print('training pipeline can be validated. Switch to TRAIN_LANG=\"ful\"')\n print('in Cell 3 for a real Fula training run using WaxalNLP.')\nelse:\n combined = concatenate_datasets(all_parts).shuffle(seed=42)\n split = combined.train_test_split(test_size=min(0.05, 200 / len(combined)))\n train_ds = split['train']\n eval_ds = split['test']\n print(f'Train: {len(train_ds)} Eval: {len(eval_ds)}')"
 
 
145
  },
146
  {
147
  "cell_type": "code",
@@ -155,7 +157,9 @@
155
  "cell_type": "markdown",
156
  "id": "md-model",
157
  "metadata": {},
158
- "source": "---\n## Model Setup — 8-bit Deep Sleep\n\n`openai/whisper-small` is loaded in **8-bit** via `BitsAndBytesConfig` to fit in T4's 15 GB VRAM.\n`prepare_model_for_kbit_training` enables gradient checkpointing and casts norms to float32.\nLoRA is injected only into the **decoder** attention projections — the encoder stays frozen,\nso gradient memory stays minimal while the language-specific output improves."
 
 
159
  },
160
  {
161
  "cell_type": "code",
@@ -262,13 +266,17 @@
262
  "id": "cell-eval",
263
  "metadata": {},
264
  "outputs": [],
265
- "source": "# ── Cell 17: WER evaluation ───────────────────────────────────────────────────\nprint('Running full evaluation on eval split ...')\neval_results = trainer.evaluate()\n\nwer_score = eval_results.get('eval_wer', float('nan'))\nprint(f'\\n📊 Final WER : {wer_score:.1%}')\nprint(f' Eval loss : {eval_results.get(\"eval_loss\", float(\"nan\")):.4f}')\n\n# Show a few example transcriptions side-by-side\nimport random, torch\nprint('\\n── Sample predictions ───────────────────────────────')\nsamples = random.sample(range(len(eval_ds)), min(5, len(eval_ds)))\nfor idx in samples:\n item = eval_ds[idx]\n feats = torch.tensor(item['input_features']).unsqueeze(0).to(model.device)\n with torch.no_grad():\n pred_ids = model.generate(\n feats.half(),\n max_new_tokens=128,\n forced_decoder_ids=model.config.forced_decoder_ids,\n )\n pred_str = processor.tokenizer.batch_decode(pred_ids, skip_special_tokens=True)[0]\n labels = [t if t != -100 else processor.tokenizer.pad_token_id\n for t in item['labels']]\n ref_str = processor.tokenizer.decode(labels, skip_special_tokens=True)\n print(f' Ref : {ref_str}')\n print(f' Pred: {pred_str}')\n print()"
 
 
266
  },
267
  {
268
  "cell_type": "markdown",
269
  "id": "md-export",
270
  "metadata": {},
271
- "source": "---\n## Export — Push LoRA Adapter to Hub\n\nThe adapter is pushed to `ous-sow/sahel-agri-adapters` under the path \n`adapters/{lang_name}/` with a Git tag like `v1.2-bambara`.\n\nThe version number is **auto-incremented** by reading existing tags on the repo\nso each training run gets a unique, traceable identifier."
 
 
272
  },
273
  {
274
  "cell_type": "code",
 
62
  "metadata": {},
63
  "outputs": [],
64
  "source": [
65
+ "# ── Cell 3: CONFIGURATION — edit these before each run ───────────────────────\nimport os\n\n# ─── Language to train ───────────────────────────────────────────────────────\n# 'bam' = Bambara 'ful' = Fula\nTRAIN_LANG = 'bam'\n\n# ─── Model ───────────────────────────────────────────────────────────────────\nWHISPER_MODEL_ID = 'openai/whisper-small'\nTARGET_SR = 16_000\n\n# ─── HuggingFace repos ───────────────────────────────────────────────────────\nHF_USERNAME = 'ous-sow'\nFEEDBACK_REPO_ID = f'{HF_USERNAME}/sahel-agri-feedback'\nADAPTER_REPO_ID = f'{HF_USERNAME}/sahel-agri-adapters'\n\n# ─── Training hyper-parameters ───────────────────────────────────────────────\nMAX_STEPS = 4_000 # T4 ~45 min; set 8000 for a deeper run\nBATCH_SIZE = 16\nGRAD_ACCUM = 2 # effective batch = 32\nLEARNING_RATE = 1e-3\nWARMUP_STEPS = 200\nSAVE_STEPS = 500\nEVAL_STEPS = 500\nLOGGING_STEPS = 50\nMAX_WAXAL_TRAIN = 5_000 # cap WaxalNLP samples (streaming budget)\nCORRECTION_REPEAT= 3 # upsample user corrections Nx for emphasis\n\n# ─── Paths (Kaggle working dir) ───────────────────────────────────────────────\nWORKING_DIR = '/kaggle/working'\nOUTPUT_DIR = f'{WORKING_DIR}/adapter_{TRAIN_LANG}'\nDATA_DIR = f'{WORKING_DIR}/data'\nAUDIO_DIR = f'{WORKING_DIR}/audio_feedback'\n\nLANG_NAME = {'bam': 'bambara', 'ful': 'fula'}.get(TRAIN_LANG, TRAIN_LANG)\n\nprint(f'Language : {TRAIN_LANG} ({LANG_NAME})')\nprint(f'Model : {WHISPER_MODEL_ID}')\nprint(f'Output : {OUTPUT_DIR}')\nprint(f'Max steps : {MAX_STEPS}')"
66
  ]
67
  },
68
  {
 
141
  "id": "cell-merge",
142
  "metadata": {},
143
  "outputs": [],
144
+ "source": [
145
+ "# -- Cell 12: Build & merge all datasets --------------------------------------\nfrom datasets import Dataset, Audio as HFAudio, concatenate_datasets\nfrom functools import partial\n\ntrain_ds = None # set here so Cell 12b can detect whether we succeeded\neval_ds = None\nall_parts = []\n\n# -- Part A: User corrections -------------------------------------------------\nif correction_records:\n print(f'Part A: {len(correction_records)} corrections x {CORRECTION_REPEAT}')\n rec_list = correction_records * CORRECTION_REPEAT\n corr_ds = Dataset.from_dict({\n 'audio': [r['local_audio'] for r in rec_list],\n 'transcription': [r.get('corrected_text') or r.get('transcription', '') for r in rec_list],\n })\n corr_ds = corr_ds.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n corr_ds = corr_ds.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=corr_ds.column_names,\n )\n all_parts.append(corr_ds)\n print(f' -> {len(corr_ds)} samples')\nelse:\n print('Part A: no corrections -- skipping')\n\n# -- Part B: WaxalNLP ---------------------------------------------------------\nif waxal_ds is not None:\n print(f'Part B: materialising up to {MAX_WAXAL_TRAIN} WaxalNLP samples ...')\n waxal_rows = list(waxal_ds.take(MAX_WAXAL_TRAIN))\n waxal_local = Dataset.from_dict({\n 'audio': [r['audio'] for r in waxal_rows],\n 'transcription': [r.get(WAXAL_TEXT_COL, '') for r in waxal_rows],\n })\n waxal_local = waxal_local.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n waxal_local = waxal_local.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=waxal_local.column_names,\n )\n all_parts.append(waxal_local)\n print(f' -> {len(waxal_local)} samples')\nelse:\n print('Part B: WaxalNLP not available -- skipping')\n\n# -- Part C: External datasets ------------------------------------------------\nfor ext_ds, text_col in external_datasets:\n ext_rows = list(ext_ds)\n if not ext_rows:\n continue\n ext_local = Dataset.from_dict({\n 'audio': [r['audio'] for r in ext_rows],\n 'transcription': [r.get(text_col, '') for r in ext_rows],\n })\n ext_local = ext_local.cast_column('audio', HFAudio(sampling_rate=TARGET_SR))\n ext_local = ext_local.map(\n partial(prepare_dataset, text_col='transcription'),\n remove_columns=ext_local.column_names,\n )\n all_parts.append(ext_local)\n print(f'Part C: {len(ext_local)} external samples')\n\n# -- Result -------------------------------------------------------------------\nprint(f'\\nData summary: {len(all_parts)} source(s) loaded')\n\nif not all_parts:\n print('No real data available for Bambara yet.')\n print('Cell 12b (below) will build a synthetic dataset so the full')\n print('training pipeline can be validated. Switch to TRAIN_LANG=\"ful\"')\n print('in Cell 3 for a real Fula training run using WaxalNLP.')\nelse:\n combined = concatenate_datasets(all_parts).shuffle(seed=42)\n n_eval = max(1, min(int(0.05 * len(combined)), 200))\n split = combined.train_test_split(test_size=n_eval)\n train_ds = split['train']\n eval_ds = split['test']\n print(f'Train: {len(train_ds)} Eval: {len(eval_ds)}')"
146
+ ]
147
  },
148
  {
149
  "cell_type": "code",
 
157
  "cell_type": "markdown",
158
  "id": "md-model",
159
  "metadata": {},
160
+ "source": [
161
+ "---\n## Model Setup - Partial Freeze Fine-Tuning\n\nopenai/whisper-small is loaded in fp16. All parameters are frozen, then\nthe last 2 decoder layers + layer norm + output projection are unfrozen (~5%\ntrainable params). No PEFT/LoRA -- avoids PEFT/transformers 5.x\nincompatibility where input_ids is passed twice to WhisperDecoder.\n"
162
+ ]
163
  },
164
  {
165
  "cell_type": "code",
 
266
  "id": "cell-eval",
267
  "metadata": {},
268
  "outputs": [],
269
+ "source": [
270
+ "# ── Cell 17: WER evaluation ───────────────────────────────────────────────────\nprint('Running full evaluation on eval split ...')\neval_results = trainer.evaluate()\n\nwer_score = eval_results.get('eval_wer', float('nan'))\nprint(f'\\n📊 Final WER : {wer_score:.1%}')\nprint(f' Eval loss : {eval_results.get(\"eval_loss\", float(\"nan\")):.4f}')\n\n# Show a few example transcriptions side-by-side\nimport random, torch\nprint('\\n── Sample predictions ───────────────────────────────')\nsamples = random.sample(range(len(eval_ds)), min(5, len(eval_ds)))\nfor idx in samples:\n item = eval_ds[idx]\n feats = torch.tensor(item['input_features']).unsqueeze(0).to(model.device)\n with torch.no_grad():\n pred_ids = model.generate(\n feats.half(),\n max_new_tokens=128,\n )\n pred_str = processor.tokenizer.batch_decode(pred_ids, skip_special_tokens=True)[0]\n labels = [t if t != -100 else processor.tokenizer.pad_token_id\n for t in item['labels']]\n ref_str = processor.tokenizer.decode(labels, skip_special_tokens=True)\n print(f' Ref : {ref_str}')\n print(f' Pred: {pred_str}')\n print()"
271
+ ]
272
  },
273
  {
274
  "cell_type": "markdown",
275
  "id": "md-export",
276
  "metadata": {},
277
+ "source": [
278
+ "---\n## Export — Push Fine-tuned Checkpoint to Hub\n\nThe adapter is pushed to `ous-sow/sahel-agri-adapters` under the path \n`adapters/{lang_name}/` with a Git tag like `v1.2-bambara`.\n\nThe version number is **auto-incremented** by reading existing tags on the repo\nso each training run gets a unique, traceable identifier."
279
+ ]
280
  },
281
  {
282
  "cell_type": "code",