175 lines
5.5 KiB
Python
175 lines
5.5 KiB
Python
import sys
|
|
import torch
|
|
import datasets
|
|
from transformers import (
|
|
AutoProcessor,
|
|
WhisperForConditionalGeneration,
|
|
Seq2SeqTrainer,
|
|
Seq2SeqTrainingArguments
|
|
)
|
|
from peft import LoraConfig, get_peft_model
|
|
from jiwer import wer
|
|
|
|
datasets.disable_caching()
|
|
|
|
# === Configuration ===
|
|
DATASET = "fsicoli/common_voice_17_0"
|
|
LANG = "ru"
|
|
SAMPLING_RATE = 16000
|
|
MODEL_NAME = "openai/whisper-small"
|
|
TRAIN_BATCH = 2
|
|
EVAL_BATCH = 2
|
|
MAX_LENGTH = 64
|
|
LR = 1e-4
|
|
MAX_STEPS = 4000
|
|
WARMUP_STEPS = 100
|
|
BATCH_SIZE = 512
|
|
|
|
print("=== Loading processor", flush=True)
|
|
processor = AutoProcessor.from_pretrained(MODEL_NAME, language=LANG, task="transcribe")
|
|
print("[+] Processor loaded", flush=True)
|
|
|
|
print("=== Loading model", flush=True)
|
|
model = WhisperForConditionalGeneration.from_pretrained(MODEL_NAME)
|
|
print("[+] Model loaded", flush=True)
|
|
|
|
print("=== Configuring LoRA", flush=True)
|
|
lora_config = LoraConfig(
|
|
task_type="SEQ_2_SEQ_LM",
|
|
target_modules=["q_proj", "k_proj", "v_proj", "out_proj"],
|
|
r=8,
|
|
lora_alpha=32,
|
|
lora_dropout=0.1,
|
|
bias="none",
|
|
fan_in_fan_out=False,
|
|
)
|
|
model = get_peft_model(model, lora_config)
|
|
print("[+] LoRA configured", flush=True)
|
|
|
|
# === Streaming train dataset ===
|
|
print("=== Loading train dataset", flush=True)
|
|
train_ds = datasets.load_dataset(DATASET, LANG, split="train")
|
|
train_ds = train_ds.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "path", "segment", "up_votes"])
|
|
print("[+] Train dataset loaded", flush=True)
|
|
|
|
# === Small cached validation/test subsets ===
|
|
print("=== Loading validation/test datasets", flush=True)
|
|
val_ds = datasets.load_dataset(DATASET, LANG, split="validation[:500]")
|
|
val_ds = val_ds.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "path", "segment", "up_votes"])
|
|
test_ds = datasets.load_dataset(DATASET, LANG, split="test[:500]")
|
|
test_ds = test_ds.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "path", "segment", "up_votes"])
|
|
print("[+] Validation/test datasets are loaded", flush=True)
|
|
|
|
# Cast audio column
|
|
print("=== Casting datasets to 16kHz", flush=True)
|
|
train_ds = train_ds.cast_column("audio", datasets.Audio(sampling_rate=SAMPLING_RATE))
|
|
val_ds = val_ds.cast_column("audio", datasets.Audio(sampling_rate=SAMPLING_RATE))
|
|
test_ds = test_ds.cast_column("audio", datasets.Audio(sampling_rate=SAMPLING_RATE))
|
|
print("[+] Audio column cast to 16kHz", flush=True)
|
|
|
|
# === Map function with picklable args ===
|
|
def prepare_examples(batch, processor_name=MODEL_NAME):
|
|
texts = batch["sentence"]
|
|
audios = [x["array"] for x in batch["audio"]]
|
|
inputs = processor(
|
|
audios,
|
|
sampling_rate=SAMPLING_RATE,
|
|
return_tensors="np"
|
|
)
|
|
batch["input_features"] = inputs.input_features
|
|
|
|
batch["labels"] = processor.tokenizer(
|
|
texts,
|
|
padding="max_length",
|
|
truncation=True,
|
|
max_length=MAX_LENGTH
|
|
).input_ids
|
|
return batch
|
|
|
|
# === Preprocessing ===
|
|
try:
|
|
print("=== Preprocessing datasets", flush=True)
|
|
train_ds = train_ds.map(
|
|
prepare_examples,
|
|
batched=True,
|
|
batch_size=BATCH_SIZE,
|
|
remove_columns=["audio", "sentence"],
|
|
load_from_cache_file=False,
|
|
)
|
|
val_ds = val_ds.map(
|
|
prepare_examples,
|
|
batched=True,
|
|
batch_size=BATCH_SIZE,
|
|
remove_columns=["audio", "sentence"],
|
|
load_from_cache_file=False,
|
|
)
|
|
test_ds = test_ds.map(
|
|
prepare_examples,
|
|
batched=True,
|
|
batch_size=BATCH_SIZE,
|
|
remove_columns=["audio", "sentence"],
|
|
load_from_cache_file=False,
|
|
)
|
|
print("[+] Datasets are preprocessed", flush=True)
|
|
except Exception as e:
|
|
print(f"[-] Preprocessing failed: {e}", flush=True)
|
|
sys.exit(1)
|
|
|
|
# === Data collator ===
|
|
def data_collator(batch):
|
|
input_features = torch.tensor([ex["input_features"] for ex in batch], dtype=torch.float32)
|
|
labels = torch.tensor([ex["labels"] for ex in batch], dtype=torch.long)
|
|
return {"input_features": input_features, "labels": labels}
|
|
|
|
# === Metrics ===
|
|
def compute_metrics(pred):
|
|
pred_ids = pred.predictions
|
|
label_ids = pred.label_ids
|
|
pred_str = processor.batch_decode(pred_ids, skip_special_tokens=True)
|
|
label_str = processor.batch_decode(label_ids, skip_special_tokens=True)
|
|
return {"wer": wer(label_str, pred_str)}
|
|
|
|
# === Training arguments ===
|
|
training_args = Seq2SeqTrainingArguments(
|
|
output_dir="./whisper_lora",
|
|
per_device_train_batch_size=TRAIN_BATCH,
|
|
gradient_accumulation_steps=1,
|
|
learning_rate=LR,
|
|
warmup_steps=WARMUP_STEPS,
|
|
max_steps=MAX_STEPS,
|
|
gradient_checkpointing=True,
|
|
predict_with_generate=True,
|
|
per_device_eval_batch_size=EVAL_BATCH,
|
|
generation_max_length=MAX_LENGTH,
|
|
save_steps=1000,
|
|
logging_steps=100,
|
|
fp16=True,
|
|
eval_strategy="steps",
|
|
eval_steps=1000,
|
|
metric_for_best_model="wer",
|
|
greater_is_better=False,
|
|
load_best_model_at_end=True,
|
|
report_to=["tensorboard"],
|
|
)
|
|
|
|
# === Trainer ===
|
|
print("=== Initializing trainer", flush=True)
|
|
trainer = Seq2SeqTrainer(
|
|
model=model,
|
|
args=training_args,
|
|
train_dataset=train_ds,
|
|
eval_dataset=val_ds,
|
|
data_collator=data_collator,
|
|
compute_metrics=compute_metrics,
|
|
)
|
|
print("[+] Trainer initialized", flush=True)
|
|
|
|
# === Start training ===
|
|
print("=== Starting training", flush=True)
|
|
trainer.train()
|
|
print("[+] Training finished", flush=True)
|
|
|
|
# === Final evaluation on test set ===
|
|
print("=== Evaluating on test set", flush=True)
|
|
results = trainer.evaluate(test_ds)
|
|
print(f"[+] Test WER: {results['eval_wer']:.4f}", flush=True) |