yalishanda's picture
Upload folder using huggingface_hub
37aeb1f verified
Raw
History Blame Contribute Delete
4.44 kB
"""Model wrappers (LightGBM, MDN) + the Engine that routes requests to them."""
from __future__ import annotations
import json
import os
import joblib
import numpy as np
import torch
from .core import predict_velocities
from ..models import heads
from ..models.model import VelocityTransformer
from ..data.seqdata import build_split_tensors, scatter_predictions
class LgbmModel:
def __init__(self, bundle):
self.model = bundle["model"]
self.cat = list(bundle["cat"])
self.drop = list(bundle["drop"])
self.cat_categories = bundle["cat_categories"]
self.best_iteration = int(bundle["best_iteration"])
def predict_all(self, df):
X = df.drop(columns=self.drop)
for c in self.cat:
X[c] = X[c].astype("category").cat.set_categories(self.cat_categories[c])
return np.asarray(self.model.predict(X, num_iteration=self.best_iteration), dtype=float)
class TransformerModel:
"""Serves the shared transformer backbone with a sampling head.
head_type selects the output head + sampler: "mdn" (temperature-controlled
mixture) or "categorical" (softmax over velocity bins; temperature ignored
by heads.sample). Both condition on genre only (not style).
"""
def __init__(self, transformer, genre_vocab, bpm_mean, bpm_std, device="cpu", head_type="mdn"):
self.m = transformer.to(device).eval()
self.genre_vocab = genre_vocab
self.bpm_mean = float(bpm_mean)
self.bpm_std = float(bpm_std)
self.device = device
self.head_type = head_type
@classmethod
def load(cls, meta_path, ckpt_path, device="cpu", head_type="mdn"):
with open(meta_path) as fh:
meta = json.load(fh)
gv = meta["genre_vocab"]
m = VelocityTransformer(n_genres=len(gv) + 1, head=head_type)
m.load_state_dict(torch.load(ckpt_path, map_location=device)["best_model"])
return cls(m, gv, meta["bpm_mean"], meta["bpm_std"], device, head_type)
def predict_all(self, df, temperature=1.0, seed=42):
t = build_split_tensors(df, self.genre_vocab, self.bpm_mean, self.bpm_std)
gen = torch.Generator().manual_seed(int(seed))
with torch.no_grad():
raw = self.m(t["voice_idx"].to(self.device), t["genre_idx"].to(self.device),
t["num_feats"].to(self.device), t["pad_mask"].to(self.device)).cpu()
s = heads.sample(self.head_type, raw, generator=gen, temperature=float(temperature))
return scatter_predictions(t["row_idx"], s, t["pad_mask"], len(df))
MdnModel = TransformerModel # backward-compatible alias (head_type defaults to "mdn")
class Engine:
def __init__(self, lgbm, mdn, cat, styles, genres):
self.lgbm = lgbm
self.mdn = mdn
self.cat = cat
self.styles = list(styles)
self.genres = list(genres)
@classmethod
def load(cls, proc_dir=os.path.join("data", "processed")):
lgbm = LgbmModel(joblib.load(os.path.join(proc_dir, "lightgbm_model.joblib")))
mdn = TransformerModel.load(os.path.join(proc_dir, "mdn_meta.json"),
os.path.join(proc_dir, "head_mdn.pt"), head_type="mdn")
cat = TransformerModel.load(os.path.join(proc_dir, "transformer_meta.json"),
os.path.join(proc_dir, "head_categorical.pt"),
head_type="categorical")
with open(os.path.join(proc_dir, "lightgbm_features.json")) as fh:
feats = json.load(fh)
lv = feats["categorical_levels"]
return cls(lgbm, mdn, cat, lv["style"], lv["genre"])
def predict(self, request):
model = request["model"]
seed = int(request.get("seed", 42))
if model == "lgbm":
predict_all = self.lgbm.predict_all
elif model == "mdn":
temp = float(request.get("temperature", 1.0))
predict_all = lambda df: self.mdn.predict_all(df, temperature=temp, seed=seed) # noqa: E731
elif model == "categorical":
predict_all = lambda df: self.cat.predict_all(df, seed=seed) # noqa: E731 (temperature n/a)
else:
raise ValueError(f"unknown model {model!r}")
return predict_velocities(request, predict_all)
def levels(self):
return {"models": ["lgbm", "mdn", "categorical"], "styles": self.styles, "genres": self.genres}