| |
| |
| |
|
|
| from dataclasses import dataclass |
| from typing import Any, Dict, List, Union |
| import os |
| import gc |
| import torch |
| import pandas as pd |
| import evaluate |
|
|
| from datasets import Dataset, Audio |
| from transformers import ( |
| WhisperForConditionalGeneration, |
| WhisperProcessor, |
| Seq2SeqTrainer, |
| Seq2SeqTrainingArguments, |
| ) |
|
|
| |
| |
| |
|
|
| MODEL_SIZE = "medium" |
| BASE_MODEL = f"neurlang/ipa-whisper-{MODEL_SIZE}" |
| OUTPUT_DIR = f"whisper-{MODEL_SIZE}-finetuned" |
|
|
| RESUME_TRAINING = False |
| RESUME_CHECKPOINT = "checkpoint-1840000" |
| RESUME_CHECKPOINT_TARGET = 1880000 |
|
|
| |
| lr = 1.251564455569462e-07 |
|
|
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| train_df = pd.read_csv("train.csv") |
| eval_df = pd.read_csv("test.csv") |
|
|
| train_df.columns = ["audio", "sentence"] |
| eval_df.columns = ["audio", "sentence"] |
|
|
| train_dataset = Dataset.from_pandas(train_df) |
| eval_dataset = Dataset.from_pandas(eval_df) |
|
|
| train_dataset = train_dataset.cast_column("audio", Audio(sampling_rate=16000)) |
| eval_dataset = eval_dataset.cast_column("audio", Audio(sampling_rate=16000)) |
|
|
| |
| train_dataset = train_dataset.shuffle(seed=42) |
|
|
| |
| |
| |
|
|
| if RESUME_TRAINING: |
| print(f"🔁 Loading processor from {OUTPUT_DIR}") |
| processor = WhisperProcessor.from_pretrained(OUTPUT_DIR) |
| else: |
| print("🆕 Creating new processor") |
| processor = WhisperProcessor.from_pretrained( |
| BASE_MODEL, |
| language="english", |
| task="transcribe", |
| predict_timestamps=True, |
| ) |
|
|
| os.makedirs(OUTPUT_DIR, exist_ok=True) |
| processor.save_pretrained(OUTPUT_DIR) |
|
|
| |
| |
| |
|
|
| def prepare_dataset(batch): |
| audio = batch["audio"] |
|
|
| batch["input_features"] = processor.feature_extractor( |
| audio["array"], |
| sampling_rate=16000 |
| ).input_features[0] |
|
|
| text = batch["sentence"] if batch["sentence"] else "" |
| batch["labels"] = processor.tokenizer( |
| text, |
| return_tensors="pt" |
| ).input_ids[0] |
|
|
| del batch["audio"] |
| del batch["sentence"] |
| return batch |
|
|
| train_dataset = train_dataset.map(prepare_dataset, num_proc=1) |
| eval_dataset = eval_dataset.map(prepare_dataset, num_proc=1) |
|
|
| |
| |
| |
|
|
| @dataclass |
| class DataCollatorSpeechSeq2SeqWithPadding: |
| processor: Any |
|
|
| def __call__(self, features): |
| inputs = [{"input_features": f["input_features"]} for f in features] |
| batch = self.processor.feature_extractor.pad( |
| inputs, return_tensors="pt" |
| ) |
|
|
| labels = [{"input_ids": f["labels"]} for f in features] |
| labels_batch = self.processor.tokenizer.pad( |
| labels, return_tensors="pt" |
| ) |
|
|
| labels = labels_batch["input_ids"].masked_fill( |
| labels_batch.attention_mask.ne(1), -100 |
| ) |
|
|
| if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all(): |
| labels = labels[:, 1:] |
|
|
| batch["labels"] = labels |
| return batch |
|
|
| data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor) |
|
|
| |
| |
| |
|
|
| cer_metric = evaluate.load("cer") |
|
|
| def compute_metrics(pred): |
| pred_ids = pred.predictions |
| label_ids = pred.label_ids |
|
|
| label_ids[label_ids == -100] = processor.tokenizer.pad_token_id |
|
|
| pred_str = processor.tokenizer.batch_decode(pred_ids, skip_special_tokens=True) |
| label_str = processor.tokenizer.batch_decode(label_ids, skip_special_tokens=True) |
|
|
| return { |
| "cer": 100 * cer_metric.compute( |
| predictions=pred_str, |
| references=label_str |
| ) |
| } |
|
|
| |
| |
| |
|
|
|
|
|
|
| training_args = Seq2SeqTrainingArguments( |
| output_dir=OUTPUT_DIR, |
| per_device_train_batch_size=4, |
| per_device_eval_batch_size=1, |
| learning_rate=lr, |
| warmup_steps=1000, |
| max_steps=RESUME_CHECKPOINT_TARGET, |
| evaluation_strategy="steps", |
| save_strategy="steps", |
| logging_steps=10*100, |
| eval_steps=10*100, |
| save_steps=10*100, |
| save_total_limit=3, |
| predict_with_generate=True, |
| generation_max_length=225, |
| fp16=False, |
| report_to=["tensorboard"], |
| load_best_model_at_end=False, |
| metric_for_best_model="cer", |
| greater_is_better=False, |
| save_safetensors=True, |
| ) |
|
|
| |
| |
| |
|
|
| if RESUME_TRAINING: |
| assert RESUME_CHECKPOINT is not None, "RESUME_CHECKPOINT must be set" |
|
|
| checkpoint_path = os.path.join(OUTPUT_DIR, RESUME_CHECKPOINT) |
| print(f"🔁 Loading model from {checkpoint_path}") |
|
|
| model = WhisperForConditionalGeneration.from_pretrained( |
| checkpoint_path, |
| torch_dtype=torch.float32, |
| ) |
| else: |
| print("🆕 Loading base model") |
| model = WhisperForConditionalGeneration.from_pretrained( |
| BASE_MODEL, |
| torch_dtype=torch.float32, |
| ) |
|
|
| |
| |
| print(f"✅ Model loaded successfully") |
| print(f" Model type: {type(model)}") |
| print(f" Device: {next(model.parameters()).device}") |
|
|
| model.to(DEVICE) |
|
|
| |
| |
| |
|
|
| trainer = Seq2SeqTrainer( |
| model=model, |
| args=training_args, |
| train_dataset=train_dataset, |
| eval_dataset=eval_dataset, |
| data_collator=data_collator, |
| compute_metrics=compute_metrics, |
| ) |
|
|
| |
| |
| |
|
|
| torch.cuda.empty_cache() |
| gc.collect() |
|
|
| if RESUME_TRAINING: |
| trainer.train(resume_from_checkpoint=checkpoint_path) |
| else: |
| trainer.train() |
|
|
| |
| |
| |
|
|
| trainer.save_model(OUTPUT_DIR) |
| processor.save_pretrained(OUTPUT_DIR) |
|
|
| print("✅ Training completed successfully") |
|
|
|
|