Integrated Music_engine and update Debug UI

This commit is contained in:
zin
2026-05-06 19:10:25 +00:00
parent dd22ee09a4
commit e6cd11b615
4 changed files with 135 additions and 118 deletions
+26 -27
View File
@@ -1,53 +1,52 @@
import numpy as np
import pandas as pd
from pathlib import Path
import joblib
class MusicMatcher:
def __init__(self, db_path: Path):
def __init__(self, db_path: Path | str, model_path: Path | str):
# 1. Загрузка базы музыки
self.music_db = pd.read_csv(db_path)
# Убедимся, что данные числовые
self.music_db['valence'] = pd.to_numeric(self.music_db['valence'], errors='coerce')
self.music_db['arousal'] = pd.to_numeric(self.music_db['arousal'], errors='coerce')
self.music_db = self.music_db.dropna()
# 2. Загрузка обученного регрессора
if Path(model_path).exists():
self.regressor = joblib.load(model_path)
print("✅ Регрессионная модель успешно загружена.")
else:
self.regressor = None
print("⚠️ ВНИМАНИЕ: Модель va_regressor.pkl не найдена!")
def predict_va(self, embedding: np.ndarray):
"""
Умный хак для демо: используем статистику вектора.
Мрачные картинки имеют меньшую среднюю активацию.
Использование обученной ML-модели (Ridge) для маппинга
эмбеддингов в пространство Valence-Arousal.
"""
# 1. Средняя сила активации (прокси для Valence)
mean_act = float(np.mean(embedding))
# 2. Разреженность вектора (прокси для Arousal)
# Чем больше нулей или близких к нулю значений, тем "спокойнее" картинка
sparsity = float(np.mean(embedding < 0.1))
# Масштабируем: если средняя активация низкая (мрачное), уводим Valence вниз
# (Типичный mean_act для ResNet обычно от 0.2 до 0.8)
v = np.interp(mean_act, [0.2, 0.6], [2.0, 8.0])
# Если много нулей (пустота/мрак), Arousal падает.
# Если нулей мало (пестрый мусор) - Arousal растет.
a = np.interp(sparsity, [0.3, 0.8], [8.0, 2.0]) # Обратная зависимость
# Добавляем "соль" из суммы вектора, чтобы избежать одинаковых результатов
# для похожих, но разных наборов картинок
salt = (float(np.sum(embedding)) % 2.0) - 1.0
v += salt
a += salt
if self.regressor is not None:
# Модель ожидает двумерный массив (batch_size, features)
emb_2d = embedding.reshape(1, -1)
prediction = self.regressor.predict(emb_2d)[0] # Получаем [Valence, Arousal]
v, a = prediction[0], prediction[1]
else:
# Fallback на случай, если файл модели потеряется
v, a = 5.0, 5.0
return np.clip(v, 1.0, 9.0), np.clip(a, 1.0, 9.0)
def get_playlist(self, user_vector: np.ndarray, top_k: int = 5):
# 1. Предсказываем координаты через ML-модель
target_v, target_a = self.predict_va(user_vector)
# Считаем Евклидово расстояние от пользователя до всех треков
# 2. Считаем Евклидово расстояние (L2-норма) до треков в базе
distances = np.sqrt(
(self.music_db['valence'] - target_v)**2 +
(self.music_db['arousal'] - target_a)**2
)
# Добавляем дистанцию, сортируем по возрастанию (чем меньше, тем ближе)
# 3. Формируем финальную выдачу
df_result = self.music_db.copy()
df_result['distance'] = distances
playlist = df_result.sort_values(by='distance').head(top_k)
Binary file not shown.