afrispeech-gender-id / scripts /gender_id.py
michsethowusu's picture
Upload scripts/gender_id.py with huggingface_hub
6130741 verified
Raw
History Blame Contribute Delete
2.6 kB
"""Reusable gender-ID pipeline: sherpa-onnx speaker embedding + this repo's ONNX head.
No custom encoder here - sherpa-onnx's pretrained 3D-Speaker CAM++ speaker-embedding
extractor (VoxCeleb, 16 kHz) does the acoustic modeling; this repo only ships the
tiny MLP head trained on top of its embeddings.
"""
from __future__ import annotations
import numpy as np
import onnxruntime as ort
import sherpa_onnx
import soundfile as sf
from huggingface_hub import hf_hub_download
EMBED_MODEL_REPO = "csukuangfj/speaker-embedding-models"
EMBED_MODEL_FILE = "3dspeaker_speech_campplus_sv_en_voxceleb_16k.onnx"
HEAD_MODEL_REPO = "AfriSpeech/afrispeech-gender-id"
TARGET_SR = 16000
class GenderClassifier:
def __init__(self, num_threads: int = 2):
embed_path = hf_hub_download(EMBED_MODEL_REPO, EMBED_MODEL_FILE)
config = sherpa_onnx.SpeakerEmbeddingExtractorConfig(
model=embed_path, num_threads=num_threads, debug=False, provider="cpu"
)
self.extractor = sherpa_onnx.SpeakerEmbeddingExtractor(config)
# config.json holds the output-index -> gender label mapping (as
# well as being one of HF Hub's default download-count query files,
# so loading it here also makes real use of this class register as a
# tracked download on the model page).
import json
head_path = hf_hub_download(HEAD_MODEL_REPO, "onnx/model.onnx")
config_path = hf_hub_download(HEAD_MODEL_REPO, "config.json")
with open(config_path, encoding="utf-8") as f:
self.label_map = json.load(f)["label_map"]
self.session = ort.InferenceSession(head_path, providers=["CPUExecutionProvider"])
def embed(self, samples: np.ndarray, sr: int) -> np.ndarray:
stream = self.extractor.create_stream()
stream.accept_waveform(sample_rate=sr, waveform=samples)
stream.input_finished()
return np.asarray(self.extractor.compute(stream), dtype=np.float32)
def predict(self, samples: np.ndarray, sr: int) -> tuple[str, float]:
embedding = self.embed(samples, sr).reshape(1, -1)
logits = self.session.run(["logits"], {"embedding": embedding})[0][0]
probs = np.exp(logits - logits.max())
probs /= probs.sum()
pred_idx = int(probs.argmax())
return self.label_map[str(pred_idx)], float(probs[pred_idx])
def predict_file(self, path: str) -> tuple[str, float]:
audio, sr = sf.read(path, dtype="float32", always_2d=False)
if audio.ndim > 1:
audio = audio.mean(axis=1)
return self.predict(audio, sr)