Skip to content

tanguy-rdt/embedded-machine-learning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tanguy ROUDAUT - Baptiste LE ROUX - Mathis LE ROUX

FIPASE 24

forthebadge forthebadge forthebadge

Au cours des 16h de Cr dédiés à cette matière nous avons conçu un programme permettant la reconnaissance de style musical d’après un extrait audio de 30 secondes. Nous avons utilisé la base de données GTZAN[1] : 1000 pistes audio de 30 secondes, format .au, avec 10 classes (blues, classique, country, Disco, Hiphop, Jazz, Metal, Pop, Reggae, Rock).

Sommaire

  1. Utilisation
  2. Extraction des descripteurs
  3. Entrainement des modèles
  4. Prédiction
  5. Mesure des performances

Utilisation

  1. Compilation

    • Sur PC de dev: $ ./run.sh setup_project
    • Sur Raspberry Pi: $ ./run.sh setup_project -t
  2. Extraction des descripteurs

    • Mode normal: $ ./run.sh create_dataset
    • Mode de debug: $ ./run.sh create_dataset -d

  3. Entrainement des modèles

    • Sur PC de dev: $ ./run.sh train_model
  4. Prédiction à l'aide des modèles

    • En Python sur PC de dev: $ ./run.sh predict -l python

    • En C++ sur PC de dev: $ ./run.sh predict -l cpp

    • En C++ sur cible: $ ./run.sh predict -l cpp (Il ne faut pas oublier que le projet doit être compilé pour la cible, option -t)

NB: Il est important de conserver la structure de nos dossiers, sinon le code ne fonctionnera pas:

  • Les fichiers audios: resources/au_files
  • Les fichiers CSV: resources/csv_files
  • Les modèle Python: resources/model
  • Les modèle C++: cpp/model

Extraction des descripteurs

Il est conseillé de réaliser l'extraction des descripteurs sur le PC de développement pour des raisons de performance. Le code étant écrit en C++ il peut être exécuter sur Raspberry PI sans problème de portabilité, mais le temps d'exécution sera rallongé.

  • Mode Normal:
    Ce mode permet d'extraire tous les descripteurs de chaque fichier audio présent dans le dossier resources/au_files. Le code cpp/audio_preprocessing permet de lire chaque fichier audio, de réaliser une FFT et de calculer les descripteurs mu et sigma des fichiers audios. Ces descripteurs sont ensuite enregistrés au format CSV dans un sous dossier portant le nom de l'audio dans le répertoire resources/csv_files. Tout au long de l'extraction, les descripteurs de chaque fichier audio sont également ajoutés au fichier resources/csv_files/dataset.csv, ce qui permet de garder une trace par fichier audio mais également d'avoir notre dataset pour réaliser nos entrainements et prédictions.

  • Mode Debug:
    Ce mode réalise les mêmes étapes que le mode normal mais seulement un fichier audio est traité. Nous avons choisi le fichier audio blues.00000.au, mais si vous souhaitez utiliser un autre vous pouvez remplacer son path par un autre dans le fichier run.sh. Le dataset sera sauvegardé dans le fichier resources/csv_files/dataset_debug.csv. A la suite de l'extraction des descripteurs, ils sont affichés à l'aide matplotlibcpp.cpp.

Entrainement des models

L'entrainement des modèles doit être réalisé sur PC de dev, la puissance du Raspberry est trop faible pour cette étape. De plus, le code étant en python il est possible de faire face à des problèmes de portabilité.

Le fichier resources/csv_files/dataset.csv créé précédement est ouvert et splité en deux autres dataset, resources/csv_files/dataset_train.csv et resources/csv_files/dataset_test.csv. Cela nous permet d'avoir une portion de dataset que nos modèles n'ont pas rencontré.
Notre dataset d'entrainement est ensuite normalisé à l'aide de StandardScaler, dont ses poids sont sauvegardés dans resources/scaler.txt. Ce fichier sera nécessaire pour normaliser notre dataset au moment de la prédiction pour avoir la même cohérence dans l'ensemble de nos dataset.

Maintenant que nos données d'entrainement sont prêtes, nous pouvons entraîner nos modèles et les convertir dans un format compatible pour nos prédictions en C++:

  • Random Forest: sklearn.ensemble.RandomForestClassifier, permet de créer notre modèle en lui passant nos descripteurs avec la fonction RandomForestClassifier.fit(). Les étapes sont simples pour l'entrainement de notre modèle et il en est de même pour la sauvegarde. On utilise le format .joblib pour réaliser nos prédictions en Python et emlearn pour convertir notre modèle en .h.
  • Decision Tree: Les étapes sont similaires pour ce modèle mais, lors de l'entrainement il faut utiliser le classifieur sklearn.tree.DecisionTreeClassifier.
  • LinearSVC: La méthode d'entrainement est la même grâce au classifieur sklearn.svm.LinearSVC.
    Ici, emlearn ne nous permet pas de convertir notre modèle. Il faut donc le réaliser manuellement, 2 possibilités s'offrent à nous, la sauvegarde des poids dans un fichier .txt ou dans un .h. Nous avons décidé de le sauvegarder dans un format .h. Plus complexe au moment de la sauvegarde puisqu'il ne faut pas faire d'erreur qui pourrait empêcher la compilation, mais un gain de temps important. Si nous avions sauvegarder nos poids dans un .txt il aurait fallut parser le fichier au moment de la prédiction ce qui est une perte de temps.
  • Neural Network: L'entraînement du modèle est cette fois-ci plus complexe. Nous utilisons TensorFlow pour créer notre modèle et ajuster ses couches. Après plusieurs tests, nous avons décidé d'utiliser une architecture comprenant plusieurs couches denses. La première couche dense a 128 neurones avec une activation 'relu' et prend en entrée les dimensions de nos données. Elle est suivie par une deuxième couche dense de 64 neurones, également avec une activation 'relu'. Après cela, nous avons une couche 'Flatten' pour aplatir les données avant de les passer à une autre couche dense de 64 neurones. Pour éviter le surajustement, nous avons intégré une couche 'Dropout' avec un taux de 0,5. Enfin, la couche de sortie comporte autant de neurones que de classes dans notre problème, avec une activation 'softmax' pour la classification multiclasse.
    Nous avons finalement sauvegardé notre modèle au format .tflite

A la fin de l'entrainement, tous nos modèles se trouvent dans le dossier resources/model. Il ne faut pas oublier de déplacer nos modèles C++ dans le dossier cpp/model.

Prédiction

Nous avons mis en place deux types de prédictions, l'une en C++ et l'autre en Python. Celle en python est plus simple à mettre en place grâce à la fonction predict(), nous l'avons principalement utilisé pour vérifier le bon fonctionnement de nos modèles. Nous allons donc décrire dans cette partie le fonctionnement de la prédiction en C++.
Au moment de la prédiction il est important d'utiliser notre dataset resources/dataset_test.csv, pour s'assurer que les données prédites sont inconnus par nos modèles.

  • Normalisation: La normalisation est une étape importante pour assurer une cohérence entre nos descripteurs utilisés pour la prédiction et ceux utilisés lors de l'entraînement des modèles. Nous avions utilisé un scaler qui est sauvegardé dans le fichier resources/scaler.txt. La méthode est simple : on l'ouvre pour récupérer nos poids, qui sont aussi nombreux que nos descripteurs, et on applique ces coefficients de normalisation à chaque descripteur de notre jeu de données. Cela permet de standardiser les données en fonction des valeurs apprises lors de l'entraînement, assurant ainsi que les valeurs d'entrée du modèle lors de la prédiction soient sur la même échelle que celles utilisées pendant l'entraînement.
  • Random Forest: La prédiction est simple, on utilise la fonction RandomForestClassifier_predict fournis par emlearn dans notre fichier .h, avec en arguments nos descripteurs et leurs nombres. La fonction nous retourne la prédiction de classe dans laquelle se trouve notre fichier audio.
  • Decision Tree: La procédure est la même grâce à la fonction DecisionTreeClassifier_predict
  • LinearSVC: Pour ce modèle, nous utilisons un codage manuel. Chaque descripteur est multiplié par un poids correspondant de notre modèle, situé au même indice, auquel on ajoute ensuite le terme de biais. Comme nous avons 10 classes, il est nécessaire de répéter cette étape 10 fois, en utilisant les 10 ensembles différents de vecteurs de poids et de biais. La multiplication de chaque descripteur par son poids associé, suivie de l'addition du biais, génère un score pour chaque classe. La classe avec le score le plus élevé est choisie comme la prédiction de notre modèle.
  • Neural Network: Pour ce modèle, nous utilisons TensorFlow Lite pour effectuer des prédictions en temps réel. Le modèle est chargé à partir du fichier .tflite créer lors de l'entrainement puis un interpréteur TensorFlow Lite est construit. Une fois l'interpreteur configuré, nous allouons les tenseurs nécessaires. Pour chaque ensemble de caractéristiques, ces dernières sont chargées dans le tenseur d'entrée. L'interpréteur exécute alors le modèle, et les scores de sortie pour chaque classe sont récupérés. Finalement il faut parcourir les scores pour identifier la classe avec le score le plus élevé, qui représente notre prédiction.
    Cette prédiction fonctionne que si le projet est compilé sur Raspberry Pi puisque tensorflowlite est nécessaire, si le code C++ est compilé pour une autre cible alors la prédiction avec le neural network sera évité.

Mesure de performance

Performance à l'entrainement (Python)

Random Forest Decision Tree Lineare SVC Neural Network
Précision 0.55 0.42 0.38 0.52
Ratio 180/330 138/330 122/330
Temps d'éxecution 16.26ms 1.46ms 2.80ms 205.24ms

Performance à la prédiction (Python)

Random Forest Decision Tree Lineare SVC Neural Network
Précision 0.54 0.41 0.38 0.52
Ratio 180/330 138/330 122/330 173/330
Temps d'éxecution 14.29ms 0.84ms 4.15ms 22.43ms

Performance à la prédiction (C++ sur PC de dev)

Random Forest Decision Tree Lineare SVC Neural Network
Précision 0.54 0.41 0.36
Ratio 180/330 138/330 122/330
Temps d'éxecution 4.38ms 0.089ms 41.291ms

Performance à la prédiction (C++ sur raspberry)

Random Forest Decision Tree Lineare SVC Neural Network
Précision 0.54 0.41 0.36 0.52
Ratio 180/330 138/330 122/330 173/330
Temps d'éxecution 9.149ms 0.444ms 127.249ms 50.723ms

On constate que notre modèle le plus performant pour la classification est le Random Forest, mais on remarque également que la prédiction en C++ à un réel intérêt puisque les performances sont bien meilleures d'un point de vue de temps d'éxecution mais également de consommation de mémoire. On peut voir ci-dessous que nos modèles .h sont moins lourds que ceux utilisés en python.

$ lsd -l resources/model/
.rw-r--r-- tanguyrdt staff  54 KB Sat Jan 27 11:27:50 2024 DecisionTreeClassifier.joblib
.rw-r--r-- tanguyrdt staff  81 KB Sat Jan 27 11:27:50 2024 LinearSVC.joblib
.rw-r--r-- tanguyrdt staff 4.1 MB Sat Jan 27 11:27:50 2024 RandomForestClassifier.joblib
.rw-r--r-- tanguyrdt staff 1.7 MB Sat Jan 27 11:27:50 2024 Sequential.joblib

$ lsd -l cpp/model/
.rw-r--r-- tanguyrdt staff 178 B  Sat Jan 27 11:27:42 2024 CMakeLists.txt
.rw-r--r-- tanguyrdt staff  44 KB Sat Jan 27 11:27:42 2024 DecisionTreeClassifier.h
.rw-r--r-- tanguyrdt staff 218 KB Sat Jan 27 11:27:42 2024 LinearSVC.h
.rw-r--r-- tanguyrdt staff 566 KB Sat Jan 27 11:27:42 2024 NeuralNetwork.tflite
.rw-r--r-- tanguyrdt staff 3.2 MB Sat Jan 27 11:27:42 2024 RandomForestClassifier.h

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages