Download code/predict.py from meet5568/meet_models: direct link, hf CLI and curl.
- Browser
- Download file 6.2 kB
-
https://huggingface.co/meet5568/meet_models/resolve/main/code/predict.py
- Command line
-
hf download hf://meet5568/meet_models/code/predict.py
-
curl -L -o predict.py https://huggingface.co/meet5568/meet_models/resolve/main/code/predict.py
6.2 kB
| #!/usr/bin/env python3 | |
| """ | |
| Predict the theme of Hindi songs (romance / sadness / celebration / motivational) with the models | |
| of meet5568/meet_models. | |
| For a NEW song, every one of the 5 cross-validation folds has its own lyrics encoder and its own | |
| classifier; the script runs all 5 and averages their probabilities. | |
| Examples | |
| # lyrics only (downloads the models from Hugging Face the first time; needs access to the repo) | |
| python predict.py --lyrics "tujhe dekha toh yeh jaana sanam, pyaar hota hai deewana sanam" | |
| python predict.py --lyrics-file song.txt | |
| # use an already downloaded copy of the repo | |
| python predict.py --model-dir /path/to/meet_models --lyrics-file song.txt | |
| # lyrics + audio: an .npz with arrays "instrument" and "mix" (and/or "vocal"), each (n_songs, 9984) | |
| python predict.py --lyrics-file song.txt --audio-npz song_audio.npz --combination lyrics+instrument+mix | |
| Python API | |
| from predict import ThemePredictor | |
| p = ThemePredictor() # or ThemePredictor(model_dir="...") | |
| p.predict(["lyrics of song 1", "lyrics of song 2"]) # DataFrame of probabilities | |
| p.predict(lyrics, audio={"instrument": A, "mix": B}, combination="lyrics+instrument+mix") | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| REPO = "meet5568/meet_models" | |
| THEMES = ["romance", "sadness", "celebration", "motivational"] | |
| AUDIO = ("vocal", "instrument", "mix") | |
| def get_model_dir(model_dir=None, need_encoders=True, need_classifiers=True): | |
| """A local copy of the repo; downloads (only the needed parts) if no path is given.""" | |
| if model_dir: | |
| return model_dir | |
| from huggingface_hub import snapshot_download | |
| patterns = ["code/*", "README.md"] | |
| if need_encoders: | |
| patterns.append("lyrics_encoder/theme_tuned/*") | |
| if need_classifiers: | |
| patterns.append("classification/*") | |
| # private repo: run `huggingface-cli login` once, or set the HF_TOKEN environment variable | |
| return snapshot_download(REPO, allow_patterns=patterns, token=os.environ.get("HF_TOKEN")) | |
| class ThemePredictor: | |
| def __init__(self, model_dir=None, device=None): | |
| self.dir = get_model_dir(model_dir) | |
| sys.path.insert(0, os.path.join(self.dir, "code")) | |
| import classify # model definitions (same code that trained the models) | |
| self.C = classify | |
| self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") | |
| def encode_lyrics(self, texts, fold): | |
| from sentence_transformers import SentenceTransformer | |
| enc = SentenceTransformer(os.path.join(self.dir, "lyrics_encoder", "theme_tuned", f"fold_{fold}"), | |
| device=self.device) | |
| enc.max_seq_length = 400 | |
| return enc.encode(texts, batch_size=16, normalize_embeddings=True, convert_to_numpy=True) | |
| def _fold_proba(self, combination, fold, inputs): | |
| d = os.path.join(self.dir, "classification", combination, f"fold_{fold}") | |
| info = json.load(open(os.path.join(d, "config.json"))) | |
| if info["method"] == "late": # average the single-modality models | |
| return np.mean([self._fold_proba(m, fold, inputs) for m in info["modalities"]], axis=0) | |
| norm = np.load(os.path.join(d, "norm.npz")) | |
| model = self.C.build_model(info["run"], info["modalities"], info["in_dims"], info["model_config"]) | |
| model.load_state_dict(torch.load(os.path.join(d, "model.pt"), map_location="cpu")) | |
| model = model.to(self.device).eval() | |
| xs = [torch.tensor((inputs[m] - norm[f"{m}_mean"]) / norm[f"{m}_std"], dtype=torch.float32, | |
| device=self.device) for m in info["modalities"]] | |
| with torch.no_grad(): | |
| return self.C.predict_proba(model, xs) | |
| def predict(self, lyrics=None, audio=None, combination=None): | |
| """lyrics: list of strings (or None); audio: dict modality -> (n, 9984) array (or None). | |
| combination: e.g. "lyrics", "lyrics+instrument+mix", "mix"; default = all given inputs.""" | |
| audio = {k: np.asarray(v, dtype=np.float32) for k, v in (audio or {}).items()} | |
| if lyrics is not None and isinstance(lyrics, str): | |
| lyrics = [lyrics] | |
| given = (["lyrics"] if lyrics is not None else []) + [m for m in AUDIO if m in audio] | |
| combination = combination or "+".join(given) | |
| mods = combination.split("+") | |
| missing = [m for m in mods if m not in given] | |
| if missing: | |
| raise ValueError(f"combination {combination} needs inputs {missing}") | |
| n = len(lyrics) if lyrics is not None else len(next(iter(audio.values()))) | |
| probs = [] | |
| for k in range(5): | |
| inputs = dict(audio) | |
| if "lyrics" in mods: | |
| inputs["lyrics"] = self.encode_lyrics(lyrics, k) | |
| probs.append(self._fold_proba(combination, k, inputs)) | |
| p = np.mean(probs, axis=0) | |
| out = pd.DataFrame(p, columns=THEMES) | |
| out.insert(0, "predicted_theme", [THEMES[i] for i in p.argmax(1)]) | |
| assert len(out) == n | |
| return out | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Predict the theme of Hindi songs") | |
| ap.add_argument("--lyrics", help="lyrics text of one song") | |
| ap.add_argument("--lyrics-file", help="text file with the lyrics of one song") | |
| ap.add_argument("--audio-npz", help=".npz with arrays vocal / instrument / mix, each (n_songs, 9984)") | |
| ap.add_argument("--combination", default=None, help="e.g. lyrics, lyrics+instrument+mix (default: all inputs given)") | |
| ap.add_argument("--model-dir", default=None, help="local copy of meet5568/meet_models (default: download)") | |
| args = ap.parse_args() | |
| lyrics = args.lyrics or (open(args.lyrics_file, encoding="utf-8").read() if args.lyrics_file else None) | |
| audio = dict(np.load(args.audio_npz)) if args.audio_npz else None | |
| if lyrics is None and audio is None: | |
| ap.error("give --lyrics / --lyrics-file and/or --audio-npz") | |
| p = ThemePredictor(args.model_dir).predict([lyrics] if lyrics else None, audio, args.combination) | |
| print(p.round(3).to_string(index=False)) | |
| if __name__ == "__main__": | |
| main() | |