Download predict.py from lothnic/audiovec: direct link, hf CLI and curl.
- Browser
- Download file 4.93 kB
-
https://huggingface.co/lothnic/audiovec/resolve/main/predict.py
- Command line
-
hf download hf://lothnic/audiovec/predict.py
-
curl -L -o predict.py https://huggingface.co/lothnic/audiovec/resolve/main/predict.py
4.93 kB
| """Inference utilities: predict emotion and extract embeddings from audio files (PyTorch).""" | |
| from pathlib import Path | |
| from typing import Optional | |
| import numpy as np | |
| import torch | |
| from audiovec.config import MAX_PAD_LEN, INPUT_SHAPE, EMOTION_MAPPING | |
| from audiovec.data import load_and_process_audio | |
| from audiovec.model import CRNNTransformer, build_transformer_model | |
| HF_REPO_ID = "lothnic/audiovec" | |
| HF_MODEL_PATH = "models/audiovec_model.pt" | |
| def ensure_model(model_path: str | Path = HF_MODEL_PATH) -> Path: | |
| """Ensure the trained model exists locally, downloading from HF Hub if needed. | |
| In a Hugging Face Space, the model is stored in the Space repo (uploaded via | |
| the API). This function downloads it on first run if it's not already present. | |
| """ | |
| model_path = Path(model_path) | |
| if model_path.exists(): | |
| return model_path | |
| # Download from Hugging Face Hub (the model is stored in the Space repo) | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| print(f"Downloading model from {HF_REPO_ID}...") | |
| # Strip the models/ prefix if present since the HF repo stores the file at root | |
| hf_filename = HF_MODEL_PATH | |
| if hf_filename.startswith("models/"): | |
| hf_filename = hf_filename[len("models/"):] | |
| downloaded = hf_hub_download( | |
| repo_id=HF_REPO_ID, | |
| filename=hf_filename, | |
| repo_type="space", | |
| local_dir=model_path.parent, | |
| local_dir_use_symlinks=False, | |
| ) | |
| print("Model downloaded successfully.") | |
| return Path(downloaded) | |
| except Exception as e: | |
| raise FileNotFoundError( | |
| f"Model not found at {model_path} and download failed: {e}. " | |
| f"Please train the model first with `uv run python train_crnn.py`" | |
| ) from e | |
| def load_trained_model( | |
| model_path: str | Path, | |
| device: torch.device | str | None = None, | |
| ) -> CRNNTransformer: | |
| """Load a trained model from disk, downloading first if needed.""" | |
| if device is None: | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| model_path = ensure_model(model_path) | |
| # Build model architecture — CRNN-Transformer (default) | |
| model = build_transformer_model( | |
| input_shape=INPUT_SHAPE, | |
| embedding_dim=256, | |
| num_classes=8, | |
| ) | |
| # Load state dict | |
| state = torch.load(str(model_path), map_location=device, weights_only=True) | |
| model.load_state_dict(state) | |
| model.to(device) | |
| model.eval() | |
| return model | |
| def predict_emotion( | |
| model: CRNNTransformer, | |
| spectrogram: np.ndarray, | |
| device: torch.device | str | None = None, | |
| ) -> tuple[str, float, np.ndarray]: | |
| """Predict emotion from a single spectrogram sample. | |
| Returns | |
| ------- | |
| emotion_label : str | |
| Predicted emotion name (e.g. "happy"). | |
| confidence : float | |
| Confidence score for the predicted emotion. | |
| all_probs : np.ndarray, shape (num_classes,) | |
| Probability distribution over all emotion classes. | |
| """ | |
| if device is None: | |
| device = next(model.parameters()).device | |
| # Add batch dimension: (1, 128, 228, 1) | |
| batch = torch.from_numpy(spectrogram[np.newaxis, ...]).float().to(device) | |
| with torch.no_grad(): | |
| logits = model(batch) | |
| probs = torch.softmax(logits, dim=-1)[0] # shape (num_classes,) | |
| probs_np = probs.cpu().numpy() | |
| pred_idx = int(np.argmax(probs_np)) | |
| confidence = float(probs_np[pred_idx]) | |
| # Map index back to emotion label (indices 0-7 → mapping 1-8) | |
| emotion_label = EMOTION_MAPPING.get(pred_idx + 1, "unknown") | |
| return emotion_label, confidence, probs_np | |
| def extract_embedding( | |
| model: CRNNTransformer, | |
| spectrogram: np.ndarray, | |
| device: torch.device | str | None = None, | |
| ) -> np.ndarray: | |
| """Extract the 256-dimensional embedding vector from a spectrogram. | |
| Returns | |
| ------- | |
| np.ndarray, shape (256,) | |
| """ | |
| if device is None: | |
| device = next(model.parameters()).device | |
| batch = torch.from_numpy(spectrogram[np.newaxis, ...]).float().to(device) | |
| with torch.no_grad(): | |
| embedding = model(batch, return_embedding=True)[0] | |
| return embedding.cpu().numpy() | |
| def predict_from_file( | |
| model_path: str | Path, | |
| audio_path: str | Path, | |
| ) -> dict: | |
| """Full inference pipeline: load model, process audio, predict + embed. | |
| Returns | |
| ------- | |
| dict with keys: | |
| emotion, confidence, probabilities, embedding, spectrogram | |
| """ | |
| model = load_trained_model(model_path) | |
| spectrogram = load_and_process_audio(audio_path, max_pad_len=MAX_PAD_LEN) | |
| emotion, confidence, probs = predict_emotion(model, spectrogram) | |
| embedding = extract_embedding(model, spectrogram) | |
| return { | |
| "emotion": emotion, | |
| "confidence": confidence, | |
| "probabilities": probs, | |
| "embedding": embedding, | |
| "spectrogram": spectrogram, | |
| } | |