From 1606679001e46e8676acf368f6d1553b1ed6b817 Mon Sep 17 00:00:00 2001 From: Simon Majorczyk <simon.majorczyk.etu@univ-lille.fr> Date: Mon, 17 Mar 2025 09:22:43 +0100 Subject: [PATCH] Changement CSS --- app_glob.py | 93 ++++++++ spotify-popularity-prediction-v2/app_glob.py | 94 ++++++++ templates/index-glob.html | 225 +++++++++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 app_glob.py create mode 100644 spotify-popularity-prediction-v2/app_glob.py create mode 100644 templates/index-glob.html diff --git a/app_glob.py b/app_glob.py new file mode 100644 index 0000000..62f8c72 --- /dev/null +++ b/app_glob.py @@ -0,0 +1,93 @@ +from flask import Flask, request, jsonify, render_template +import pickle +import pandas as pd +from sklearn.preprocessing import StandardScaler +import numpy as np + +app = Flask(__name__) + +# Charger le modèle +with open('random_forest_model_binaire.pkl', 'rb') as model_file: + rf = pickle.load(model_file) + +# Charger le scaler entraîné +with open('scaler_binaire.pkl', 'rb') as scaler_file: + scaler = pickle.load(scaler_file) + +@app.route('/') +def home(): + return render_template('index-glob.html') + +@app.route('/predict', methods=['POST']) +def predict(): + return process_prediction(request.form, '/predict') + +@app.route('/predict_sup0', methods=['POST']) +def predict_sup0(): + return process_prediction(request.form, '/predict_sup0') + +def process_prediction(form_data, endpoint): + data = form_data.to_dict() + + if 'name' in data: + data['nb_caracteres_sans_espaces'] = len(data['name'].replace(" ", "")) + if 'artists' in data: + data['nb_artistes'] = data['artists'].count(',') + 1 + data['featuring'] = int(data['nb_artistes'] > 1) + if 'duration_ms' in data: + duration_ms = float(data['duration_ms']) + data['duree_minute'] = float(f"{int(duration_ms // 60000)}.{int((duration_ms % 60000) // 1000):02d}") + if 'year' in data: + year = int(data['year']) + data['categorie_annee'] = 3 if year < 1954 else 2 if year < 2002 else 1 + if 'tempo' in data: + tempo = float(data['tempo']) + if 40 <= tempo < 60: + data['categorie_tempo'] = 1 + elif 60 <= tempo < 66: + data['categorie_tempo'] = 2 + elif 66 <= tempo < 76: + data['categorie_tempo'] = 3 + elif 76 <= tempo < 108: + data['categorie_tempo'] = 4 + elif 108 <= tempo < 120: + data['categorie_tempo'] = 5 + elif 120 <= tempo < 163: + data['categorie_tempo'] = 6 + elif 163 <= tempo < 200: + data['categorie_tempo'] = 7 + elif 200 <= tempo <= 208: + data['categorie_tempo'] = 8 + else: + data['categorie_tempo'] = 9 + + # Supprimer les clés non utilisées directement + data.pop('name', None) + data.pop('artists', None) + data.pop('duration_ms', None) + + # Convertir les valeurs en float si possible + for key in data: + try: + data[key] = float(data[key]) + except ValueError: + pass + + expected_features = ['year', 'acousticness', 'danceability', 'energy', 'explicit', + 'instrumentalness', 'key', 'liveness', 'loudness', 'mode', + 'speechiness', 'tempo', 'valence', 'nb_caracteres_sans_espaces', + 'nb_artistes', 'featuring', 'duree_minute', 'categorie_annee', 'categorie_tempo'] + + input_data = pd.DataFrame([[data.get(key, 0) for key in expected_features]], columns=expected_features) + + missing_cols = [col for col in expected_features if col not in input_data.columns] + if missing_cols: + return jsonify({'error': f'Missing features: {missing_cols}'}), 400 + + input_data_scaled = scaler.transform(input_data) + predictions = rf.predict(input_data_scaled) + + return jsonify({'predictions': int(predictions[0])}) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/spotify-popularity-prediction-v2/app_glob.py b/spotify-popularity-prediction-v2/app_glob.py new file mode 100644 index 0000000..8c9e52e --- /dev/null +++ b/spotify-popularity-prediction-v2/app_glob.py @@ -0,0 +1,94 @@ +from flask import Flask, request, jsonify, render_template +import pickle +import pandas as pd +from sklearn.preprocessing import StandardScaler +import numpy as np + +app = Flask(__name__) + +# Charger le modèle +with open('random_forest_model_binaire.pkl', 'rb') as model_file: + rf = pickle.load(model_file) + +# Charger le scaler entraîné +with open('scaler_binaire.pkl', 'rb') as scaler_file: + scaler = pickle.load(scaler_file) + +@app.route('/') +def home(): + return render_template('index-glob.html') + +@app.route('/predict', methods=['POST']) +def predict(): + # Récupérer les données du formulaire + data = request.form.to_dict() + + # Calculer automatiquement les features + if 'name' in data: + data['nb_caracteres_sans_espaces'] = len(data['name'].replace(" ", "")) + if 'artists' in data: + data['nb_artistes'] = data['artists'].count(',') + 1 + data['featuring'] = int(data['nb_artistes'] > 1) + if 'duration_ms' in data: + duration_ms = float(data['duration_ms']) + data['duree_minute'] = float(f"{int(duration_ms // 60000)}.{int((duration_ms % 60000) // 1000):02d}") + if 'year' in data: + year = int(data['year']) + data['categorie_annee'] = 3 if year < 1954 else 2 if year < 2002 else 1 + if 'tempo' in data: + tempo = float(data['tempo']) + if 40 <= tempo < 60: + data['categorie_tempo'] = 1 + elif 60 <= tempo < 66: + data['categorie_tempo'] = 2 + elif 66 <= tempo < 76: + data['categorie_tempo'] = 3 + elif 76 <= tempo < 108: + data['categorie_tempo'] = 4 + elif 108 <= tempo < 120: + data['categorie_tempo'] = 5 + elif 120 <= tempo < 163: + data['categorie_tempo'] = 6 + elif 163 <= tempo < 200: + data['categorie_tempo'] = 7 + elif 200 <= tempo <= 208: + data['categorie_tempo'] = 8 + else: + data['categorie_tempo'] = 9 + + # Supprimer les clés non utilisées directement + data.pop('name', None) + data.pop('artists', None) + data.pop('duration_ms', None) + + # Convertir les valeurs en float si possible + for key in data: + try: + data[key] = float(data[key]) + except ValueError: + pass # Garder les valeurs non convertibles (ex: texte) + + # Liste des features dans le bon ordre (comme lors de l'entraînement) + expected_features = ['year', 'acousticness', 'danceability', 'energy', 'explicit', + 'instrumentalness', 'key', 'liveness', 'loudness', 'mode', + 'speechiness', 'tempo', 'valence', 'nb_caracteres_sans_espaces', + 'nb_artistes', 'featuring', 'duree_minute', 'categorie_annee', 'categorie_tempo'] + + # S'assurer que les colonnes du DataFrame correspondent à celles du modèle, dans le bon ordre + input_data = pd.DataFrame([[data.get(key, 0) for key in expected_features]], columns=expected_features) + + # Vérifier que toutes les colonnes attendues sont présentes + missing_cols = [col for col in expected_features if col not in input_data.columns] + if missing_cols: + return jsonify({'error': f'Missing features: {missing_cols}'}), 400 + + # Normalisation des features + input_data_scaled = scaler.transform(input_data) + + # Prédiction + predictions = rf.predict(input_data_scaled) + + return jsonify({'predictions': int(predictions[0])}) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/templates/index-glob.html b/templates/index-glob.html new file mode 100644 index 0000000..cf39f73 --- /dev/null +++ b/templates/index-glob.html @@ -0,0 +1,225 @@ +<!DOCTYPE html> +<html lang="fr"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Prédiction de Popularité</title> + <style> + body { + font-family: Arial, sans-serif; + margin: 0; + display: flex; + } + /* Sidebar */ + .sidebar { + width: 200px; + background-color: #333; + color: white; + height: 100vh; + padding-top: 20px; + display: flex; + flex-direction: column; + align-items: center; + } + .sidebar button { + width: 80%; + padding: 10px; + margin: 10px 0; + border: none; + background: #444; + color: white; + cursor: pointer; + font-size: 16px; + text-align: center; + } + .sidebar button:hover { + background: #555; + } + .content { + flex-grow: 1; + padding: 20px; + } + /* Cacher les sections par défaut */ + .tab-content { + display: none; + } + .active { + display: block; + } + form { + max-width: 600px; + margin: auto; + } + label { + display: block; + margin-top: 10px; + font-weight: bold; + } + input[type="text"], input[type="number"] { + width: 100%; + padding: 8px; + margin-top: 5px; + border: 1px solid #ccc; + border-radius: 4px; + } + button { + margin-top: 20px; + padding: 10px 20px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + } + button:hover { + background-color: #45a049; + } + #result { + margin-top: 20px; + font-size: 1.2em; + color: #555; + } + </style> +</head> +<body> + + <!-- Sidebar pour naviguer entre les onglets --> + <div class="sidebar"> + <button onclick="showTab('tab1')">Prédiction Standard</button> + <button onclick="showTab('tab2')">Prédiction (>0)</button> + </div> + + <!-- Contenu principal --> + <div class="content"> + <h1>Prédire la Popularité</h1> + + <!-- Formulaire 1 : Prédiction Standard --> + <div id="tab1" class="tab-content active"> + <form id="predictionForm"> + <label for="name">Titre de la chanson :</label> + <input type="text" id="name" name="name" required> + + <label for="year">Année (Sous format YYYY):</label> + <input type="number" step="0.0001" id="year" name="year" required> + + <label for="acousticness">Acousticness (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="acousticness" name="acousticness" required> + + <label for="danceability">Danceability (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="danceability" name="danceability" required> + + <label for="energy">Energy (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="energy" name="energy" required> + + <label for="explicit">Explicit (0 ou 1 en entier):</label> + <input type="number" id="explicit" name="explicit" required> + + <label for="instrumentalness">Instrumentalness (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="instrumentalness" name="instrumentalness" required> + + <label for="key">Key (0 à 11 en chiffre entier):</label> + <input type="number" id="key" name="key" required> + + <label for="liveness">Liveness (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="liveness" name="liveness" required> + + <label for="loudness">Loudness (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="loudness" name="loudness" required> + + <label for="mode">Mode (0 ou 1 en entier):</label> + <input type="number" id="mode" name="mode" required> + + <label for="speechiness">Speechiness (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="speechiness" name="speechiness" required> + + <label for="tempo">Tempo (Sous format d'un nombre décimal jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="tempo" name="tempo" required> + + <label for="valence">Valence (0 à 1 jusqu'à 4 chiffres après la virgule):</label> + <input type="number" step="0.0001" id="valence" name="valence" required> + + <button type="button" onclick="sendPrediction('/predict')">Prédire</button> + </form> + </div> + + <!-- Formulaire 2 : Prédiction (>0) --> + <div id="tab2" class="tab-content"> + <form id="predictionFormSup0"> + <label for="name">Titre de la chanson :</label> + <input type="text" id="name" name="name" required> + + <label for="year">Année :</label> + <input type="number" id="year" name="year" required> + + <label for="acousticness">Acousticness :</label> + <input type="number" step="0.0001" id="acousticness" name="acousticness" required> + + <label for="danceability">Danceability :</label> + <input type="number" step="0.0001" id="danceability" name="danceability" required> + + <label for="energy">Energy :</label> + <input type="number" step="0.0001" id="energy" name="energy" required> + + <label for="explicit">Explicit (0 ou 1) :</label> + <input type="number" id="explicit" name="explicit" required> + + <label for="instrumentalness">Instrumentalness :</label> + <input type="number" step="0.0001" id="instrumentalness" name="instrumentalness" required> + + <label for="key">Key :</label> + <input type="number" id="key" name="key" required> + + <label for="liveness">Liveness :</label> + <input type="number" step="0.0001" id="liveness" name="liveness" required> + + <label for="loudness">Loudness :</label> + <input type="number" step="0.0001" id="loudness" name="loudness" required> + + <label for="mode">Mode :</label> + <input type="number" id="mode" name="mode" required> + + <label for="speechiness">Speechiness :</label> + <input type="number" step="0.0001" id="speechiness" name="speechiness" required> + + <label for="tempo">Tempo :</label> + <input type="number" step="0.0001" id="tempo" name="tempo" required> + + <label for="valence">Valence :</label> + <input type="number" step="0.0001" id="valence" name="valence" required> + + <button type="button" onclick="sendPrediction('/predict_sup0', 'predictionFormSup0')">Prédire (>0)</button> + </form> + </div> + + <div id="result"></div> + </div> + + <script> + // Fonction pour afficher un onglet et cacher l'autre + function showTab(tabId) { + document.querySelectorAll('.tab-content').forEach(tab => { + tab.style.display = "none"; + }); + document.getElementById(tabId).style.display = "block"; + } + + // Fonction pour envoyer la requête au bon endpoint + async function sendPrediction(endpoint, formId = "predictionForm") { + const form = document.getElementById(formId); + const formData = new FormData(form); + + const response = await fetch(endpoint, { + method: "POST", + body: formData + }); + + const result = await response.json(); + document.getElementById("result").innerText = `Prédiction: ${result.predictions}`; + } + + // Afficher l'onglet par défaut + showTab('tab1'); + </script> + +</body> +</html> -- GitLab