File size: 4,714 Bytes
bf356c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import warnings

warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import pickle

MODEL_PATH = "genre_model.pkl"
SCALER_PATH = "genre_scaler.pkl"
ENCODER_PATH = "genre_encoder.pkl"

NUMERICAL_FEATURES = [
    "melody_complexity (vocals)",
    "melody_range (vocals)",
    "melody_variability (vocals)",
    "tempo_bpm_original (mix)",
    "danceability custom (mix)",
    "loudness_integrated_lufs custom (mix)",
    "loudness_range_lu custom (mix)",
    "energy_librosa (mix)",
    "energy_librosa_std (mix)",
    "energy_essentia (mix)",
    "energy_essentia_std (mix)",
    "energy_combined (mix)",
    "spectral_centroid_mean custom (mix)",
    "mfcc_mean_1 (mix)",
    "mfcc_mean_2 (mix)",
    "chroma_mean (mix)",
    "spectral_contrast_mean (mix)",
    "repetition_score custom (mix)",
    "pitch_mean (mix)",
    "pitch_std (mix)",
    "rms_energy_mean (mix)",
    "rms_energy_std (mix)",
    "zero_crossing_rate (mix)",
]


def engineer_features(df, feature_cols):
    df = df.copy()
    df['energy_per_tempo'] = df['energy_combined (mix)'] / (df['tempo_bpm_original (mix)'] + 1)
    df['dance_energy_ratio'] = df['danceability custom (mix)'] * df['energy_combined (mix)']
    df['loudness_range_ratio'] = df['loudness_range_lu custom (mix)'] / (
                abs(df['loudness_integrated_lufs custom (mix)']) + 1)
    df['melody_energy'] = df['melody_variability (vocals)'] * df['energy_combined (mix)']
    df['spectral_complexity'] = df['spectral_centroid_mean custom (mix)'] * df['spectral_contrast_mean (mix)']
    df['mfcc_ratio'] = df['mfcc_mean_1 (mix)'] / (abs(df['mfcc_mean_2 (mix)']) + 1)
    df['rhythm_strength'] = df['tempo_bpm_original (mix)'] * df['danceability custom (mix)']
    df['pitch_variation'] = df['pitch_std (mix)'] / (df['pitch_mean (mix)'] + 1)
    df['rms_energy_ratio'] = df['rms_energy_mean (mix)'] / (df['rms_energy_std (mix)'] + 1)
    df['chroma_energy'] = df['chroma_mean (mix)'] * df['energy_combined (mix)']
    df['zero_tempo'] = df['zero_crossing_rate (mix)'] * df['tempo_bpm_original (mix)']
    df['tempo_category'] = np.where(df['tempo_bpm_original (mix)'] < 100, 0,
                                    np.where(df['tempo_bpm_original (mix)'] < 130, 1, 2))
    df['energy_category'] = np.where(df['energy_combined (mix)'] < 0.3, 0,
                                     np.where(df['energy_combined (mix)'] < 0.6, 1, 2))
    df['dance_category'] = np.where(df['danceability custom (mix)'] < 0.5, 0,
                                    np.where(df['danceability custom (mix)'] < 0.75, 1, 2))
    engineered = ['energy_per_tempo', 'dance_energy_ratio', 'loudness_range_ratio', 'melody_energy',
                  'spectral_complexity',
                  'mfcc_ratio', 'rhythm_strength', 'pitch_variation', 'rms_energy_ratio', 'chroma_energy', 'zero_tempo',
                  'tempo_category', 'energy_category', 'dance_category']
    return df, feature_cols + engineered


class GenrePredictor:
    def __init__(self):
        with open(MODEL_PATH, "rb") as f:
            self.model = pickle.load(f)
        with open(SCALER_PATH, "rb") as f:
            self.scaler = pickle.load(f)
        with open(ENCODER_PATH, "rb") as f:
            self.genre_encoder, self.all_subgenres, self.all_features = pickle.load(f)

    def predict(self, feature_dict):
        df = pd.DataFrame([feature_dict])
        df, _ = engineer_features(df, NUMERICAL_FEATURES)

        for col in self.all_features:
            if col not in df.columns:
                df[col] = 0

        values = df[self.all_features].values[0]
        values = np.nan_to_num(values, nan=0, posinf=0, neginf=0)

        input_scaled = self.scaler.transform(values.reshape(1, -1))
        genre_idx = self.model.predict(input_scaled)[0]
        genre = self.genre_encoder.inverse_transform([genre_idx])[0]

        genre_probs = self.model.predict_proba(input_scaled)[0]
        top_indices = np.argsort(genre_probs)[::-1][:5]
        similar = [(self.genre_encoder.classes_[i], genre_probs[i]) for i in top_indices]

        related_subs = [s for s in self.all_subgenres if genre.lower() in s.lower()]
        if not related_subs:
            related_subs = self.all_subgenres[:10]

        return {
            "genre": genre,
            "similar_genres": similar,
            "subgenres": related_subs
        }


df = pd.read_csv("all_genres_clean.csv",low_memory=False)
df_input = df[NUMERICAL_FEATURES]

model = GenrePredictor()
answer = model.predict(df_input.iloc[105148].to_dict())
print(answer)

print("Original :", df[["genre","sub_genres"]].iloc[105148])