SE5 ECEAI/eceai 2023/2024/GodardDelcourt

De wiki-se.plil.fr
Révision datée du 28 janvier 2024 à 16:05 par Egodard (discussion | contributions) (→‎Apprentissage avec des données en temps réel)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

Introduction

Objectifs du projet

Le projet vise à distribuer le calcul à différents endroits du réseau pour optimiser les performances et la sobriété énergétique. Un réseau d'objets connectés, de passerelles, et d'un serveur sera mis en place pour collecter, traiter, et évaluer les données environnementales.

Les données que nous avons décidé de traiter seront les différentes lettres de l'alphabet en langue des signes. L'utilisateur devra réaliser la lettre souhaitée au dessus du capteur, notre intelligence embarquée devra alors déterminer quelle lettre est réalisée et envoyer cette donnée sur un serveur pour afficher sur une page web le résultat.

Alphabet-dactylologique-nos-mains-vous-parlent.jpg

Mise en œuvre du réseau

Configuration de la Machine Virtuelle

Nous allons devoir créer une machine virtuelle sur Chassiron avec un accès internet IPv6.
Cette machine virtuelle sera déployée grâce à Xen et sera nommée "delgodVM".
La commande lancée pour créer la machine est la suivante :

xen-create-image --hostname delgodVM --force --dist bookworm --size 10G --memory 10G --dir /usr/local/xen --password glopglop --dhcp --bridge bridgeStudents

Nous allons par la suite devoir configurer son réseau, sa résolution DNS, et ses sources de paquets Debian.
Pour configurer le réseau de notre serveur, nous allons devoir modifier le fichier /etc/network/interfaces qui est un fichier de configuration réseau dans lequel les paramètres de l'interface sont spécifiés.

auto enX0
iface enX0 inet6 auto

Pour configurer la résolution DNS de notre serveur, nous allons modifier le fichier /etc/resolv.conf. Ce fichier permet de spécifier la configuration des serveurs de nom (DNS) pour résoudre les noms de domaine en adresses IP. Nous allons donc réaliser la configuration DNS suivante fournie dans le sujet :

domain plil.info
search plil.info
nameserver 2a01:c916:2047:c800:216:3eff:fe82:8a5c

Pour modifier les sources des paquets Debian, nous allons modifier le fichier /etc/apt/sources.list en rajoutant les paquets suivants :

deb     http://deb.debian.org/debian     bookworm main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian     bookworm main contrib non-free non-free-firmware

Et nous allons aussi supprimer le fichier /etc/apt/apt.conf.d/01proxy.

Raspberry Pi

Nous allons tout d'abord devoir configurer la Raspberry Pi avec une image que nous avons récupéré sur internet.

Ensuite, nous allons devoir connecter la Raspberry Pi au WiFi nommé WIFI_IE_1. Pour configurer la Raspberry Pi, nous allons la connecter en série à un ordinateur. Pour savoir quels pins il est nécessaire de connecter, il faut se fier à l'image ci-dessous. La partie alimentation n'est pas à connecter car trop faible pour alimenter la Raspberry Pi, nous allons donc devoir brancher sur secteur la carte grâce à une alimentation USB.

Learn raspberry pi piconsole bb.png


Après avoir connecté la Raspberry Pi sur l'ordinateur, nous pouvons vérifier sur quelle interface la carte est connectée avec la commande

 ls /dev

Par exemple, ici la carte était sur ttyUSB0. Nous pouvons ensuite lancer une liaison série avec minicom grâce à la commande

minicom -s

et les paramètres suivants ttyUSB0 et 115200 8N.

Nous allons ensuite pouvoir configurer le wifi. Pour cela, nous allons dans un terminal de la Raspberry Pi, il faut alors taper la commande suivante afin d'accéder à la configuration du système :

raspi-config

Puis nous sommes allées dans ""System Options"" afin de pouvoir configurer les différentes informations qui vont nous permettre d'avoir le WiFi sur la carte.
Pour vérifier si nos modifications ont bien fonctionné, nous avons réalisé la commande ip a pour vérifier si le wlan0 était bien Up, et nous avons aussi ping google.com pour vérifier si le WiFi fonctionnait. Tout cela était correct.


Informations concernant la Raspberry Pi :
Adresse ip : 172.26.145.129
User pour le SSH : pifou


Objets Connectés

Utilisation d'un STM32F401RE avec un capteur de distance (Nucleo-53L5A1).

Protocole de communication et acquisition des données.

Acquisition des données

Nous allons devoir développer un firmware embarqué afin collecter des données, nous allons pour cela utiliser un code STM32 Cube IDE qui va nous aider à collecter les données du capteur.

La première étape consiste alors a installer STM32 Cube IDE pour l'objet connecté (STM32F401RE) avec capteur de distance (Nucleo-53L5A1).

Cependant, nous avons eu de nombreux problème pour installer ce logiciel sur les zabeths de l'école. Nous avons donc du utiliser un ordinateur personnel sous windows afin d'installer STM32CubeIDE. Afin de téléverser le programme sur notre objet connecté muni du capteur, nous allons devoir connecter celui-ci en série avec notre ordinnateur. Le code sera par la suite placé sur l'objet grâce à l'application.

Pour récupérer les données, nous allons utiliser un programme d'exemple fourni par STMicroelectronics. Pour vérifier le fonctionnement de ce code, nous avons lancé une liaison série sur l'objet connecté (grâce à Putty par exemple) et nous avons bien pu récupérer des données.

Cependant, ce code ne nous convenait pas, nous avons donc du le modifier afin d'obtenir des données brutes affichées par paquets de 16 valeurs.

static void print_result(RANGING_SENSOR_Result_t *Result)
{
	int8_t j, k, l;
	    uint8_t zones_per_line;
	    zones_per_line = ((Profile.RangingProfile == RS_PROFILE_8x8_AUTONOMOUS) ||
	           (Profile.RangingProfile == RS_PROFILE_8x8_CONTINUOUS)) ? 8 : 4;
	    for (j = 0; j < Result->NumberOfZones; j += zones_per_line)
	    {
	      for (l = 0; l < RANGING_SENSOR_NB_TARGET_PER_ZONE; l++)
	      {
	        /* Print distance and status */
	        for (k = (zones_per_line - 1); k >= 0; k--)
	        {
	   	   if((l!=0) || (k!=(zones_per_line - 1)) || (j!=0)){
	   	       	  printf(";");
	   	 }
	          if (Result->ZoneResult[j+k].NumberOfTargets > 0)
	            printf("%ld",(long)Result->ZoneResult[j+k].Distance[l]);
	          else
	            printf("0");
	        }
	      }
	    }
 printf("\r\n");
}

Protocole de communication

Raspberry Pi & capteur

Tout d'abord, nous allons devoir mettre en oeuvre un protocole de communication permettant l'envoi de données entre la carte (Raspberry Pi) et le capteur.

Nous avons alors décidé de créer un script en Python qui permet au capteur de communiquer ses données à la Raspberry Pi, grâce à la liaison série entre les deux appareils. Comme nous avons vu précédemment, le code installé sur le capteur permet l'envoi de données brutes sur la liaison série, de la forme suivante :

Données capteur csv.png

Le code Python que nous avons implémenté sur la Raspberry Pi va permettre de récupérer ces données. Le script se trouve à l'endroit suivant sur la carte ~/Documents/lecture/reader.py. Lors de son lancement, celui ci va obtenir les données envoyées par le capteur, puis il va ranger ces données dans un fichier nommé store_data.csv. Vous pouvez ainsi voir sur la photo ci-dessus l'affichage du fichier store_data.csv se trouvant sur la Raspberry Pi et contenant les données du capteur.

Le script que nous avons écrit est le suivant :

import serial
ser = serial.Serial (
    # Port série pour lire les données
    port='/dev/ttyACM0',
    # Débit de la communication série
    baudrate=460800,
    # Check pour la parité, ici on ne le fait pas
    parity=serial.PARITY_NONE,
    # Nombre de commandes séries à accepter avant expiration du délai
    timeout=1
)

# Ouverture du fichier "store_data.csv"
file = open(r"store_data.csv","w")

# Mise en pause du programme pendant 1 seconde pour éviter de surcharger le port série
while 1:
    x=ser.readline()
    output=str(x, 'UTF-8')
    file.write(output)
    print (x)

file.close()

Raspberry Pi et serveur

Nous allons ensuite mettre en oeuvre un protocole de communication entre la Raspberry Pi et le serveur que nous avons créé. D'après les consignes du sujet, nous devons faire en sorte que ce protocole soit facilement étendu à un grand nombre de Raspberry Pi. C'est pourquoi après quelques recherches, nous avons décide de réaliser un protocole de communication HTTP initié avec un script Python.

Tout d'abord, un script Python sera réalisé sur notre serveur nommé delgodVM pour le spécialiser en un serveur web qui va nous permettre de gérer les requêtes HTTP. Ainsi, sur le serveur delgodVM, nous allons pouvoir lancer notre serveur Python HTTP avec le script créé et avoir le retour suivant :

Lancement serveur http.png

Le script est le suivant :

import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
import json

PORT = 8080
class MyHandler(SimpleHTTPRequestHandler):
  def do_GET(self):
	if self.path == '/ip':
  	self.send_response(200)
  	self.send_header('Content-type', 'text/html')
  	self.end_headers()
  	self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode())
  	return
	else:
  	return SimpleHTTPRequestHandler.do_GET(self)
    

  def do_POST(self):
	if self.path== '/data':
  	content_length = int(self.headers['Content-Length'])
  	data = self.rfile.read(content_length).decode('utf-8')
  	data_dict = json.loads(data)

  	print("Donnees recues:", data_dict)

  	self.send_response(200)
  	#Ouverture du fichier pour stocker les donnees
  	file = open(r"store_data.csv","a")
 	 
  	#Donnes ecrites dans le fichier .csv
  	file.write(data_dict)
 	 
	else :
  	self.send_response(404)
  	self.end_headers()

class HTTPServerV6(HTTPServer):
    address_family = socket.AF_INET6

def main():
  server = HTTPServerV6(('::',PORT),MyHandler)
  print("Serveur actif sur le port :", PORT)
  server.serve_forever()  

if __name__ == '__main__':
  main()

Pour vérifier (de plus) si notre serveur est bien lancé, nous allons essayer d'y accéder grâce à une page web sur une zabeth de l'école à l'adresse suivante sur le port 8080 configuré dans le fichier de configuration du serveur Python http : http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080

Accès serveur web.png

De la Raspberry Pi, on vérifie qu’on peut bien accéder au serveur Python HTTP :

Test accès serveur avec curl.png

Pour envoyer des données de la Raspberry PI au serveur HTTP, nous avons créé un script Python qui va permettre d'envoyer ces données par des requêtes HTTP. Ce script sera bien évidemment lancé de la Raspberry PI. Pour que l'envoi de données fonctionne, nous devons alors lancer le script permettant le fonctionnement du serveur web sur le serveur delgodVM (commande python3 ./server.py). Et nous devons lancer simultanément le script reader.py sur la Raspberry PI. Le script est le suivant :

import serial
import requests

ser = serial.Serial (
    # Port série pour lire les data
    port='/dev/ttyACM0',
    # Débit de la communication série
    baudrate=460800,
    # Check ds parités, ici on ne le fait pas
    parity=serial.PARITY_NONE,
    # Number of serial commands to accept before timing out
    timeout=1
)

# Open function to open the file "store_data.csv"
#file = open(r"store_data.csv","w")

url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data"

while 1:
    url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data"
    x=ser.readline()
    output=str(x, 'UTF-8')
#   file.write(output)
    data = output
    response = requests.post(url,json=data)
    print (x)
    if reponse.status_code == 200:
      print("Donnees envoyees avec succes")
    else:
      print(f"Échec de l'envoi. Code de statut : {response.status_code}")
      print(response.text)

#file.close()

Au final ,nous arrivons bien à recevoir les données sur le serveur Xen, par un paquet unique.

Reception des données.png

Stockage des Données

Notre serveur Xen reçoit maintenant les données de la carte grâce aux requêtes HTTP.

Nous avons alors décidé de code un script python recevant les données et qui les stockes dans un fichier .csv sur le serveur afin de pouvoir par la suite utiliser les données du capteur. Ainsi, vous pouvez retrouver dans le script du server Python http, certaines parties correspondant à l'enregistrement des données reçues dans un fichier lui aussi nommé store_data.csv se retrouvant bien évidemment sur notre serveur Xen. Le code est le même que précédemment :

import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
import json

PORT = 8080
class MyHandler(SimpleHTTPRequestHandler):
  def do_GET(self):
	if self.path == '/ip':
  	self.send_response(200)
  	self.send_header('Content-type', 'text/html')
  	self.end_headers()
  	self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode())
return
	else:
  	return SimpleHTTPRequestHandler.do_GET(self)

  def do_POST(self):
	if self.path== '/data':
  	content_length = int(self.headers['Content-Length'])
  	data = self.rfile.read(content_length).decode('utf-8')
  	data_dict = json.loads(data)

  	print("Donnees recues:", data_dict)

  	self.send_response(200)
  	#Ouverture du fichier pour stocker les donnees
  	file = open(r"store_data.csv","w")

  	#Donnes ecrites dans le fichier .csv
  	file.write(data_dict)


else :
  	self.send_response(404)
  	self.end_headers()

class HTTPServerV6(HTTPServer):
    	address_family = socket.AF_INET6

def main():
  server = HTTPServerV6(('::',PORT),MyHandler)
  print("Serveur actif sur le port :", PORT)
  server.serve_forever()  

if __name__ == '__main__':
  main()

Et voici le contenu du fichier .csv sur notre serveur :

Fichier csv serveur.png


Les données sont donc correctement transférées du capteur vers la Raspberry Pi en liaison série. Puis par des requêtes http, les données seront envoyées par la Raspberry Pi jusqu'au serveur qui stockera ces données dans un fichier .csv pour par la suite les analyser.

Entraînement du modèle d’apprentissage

Entraînement sur NanoEdge AI Studio

Description

Pour commencer la partie Entraînement, nous avons décidé de commencer par NanoEdge AI Studio. Ce logiciel développé par STMicroelectronics

permet de "créer" une Intelligence Artificielle (IA), de manière simple et guidée.

Pour créer cette IA, nous avons tout d'abord dû connecter la carte STMicroelectronics au PC et lancer NanoEdge AI Studio.

Signaux

Après avoir créé un projet "n-Class", nous avons dû rentrer les signaux que nous voulions faire apprendre. Dans notre cas, ce sont les lettres de A à J qui vont être apprises.

SignauxNEAIS.png

Après avoir fait tous les signaux que nous avons besoins, nous devons lancer le Benchmark.

Benchmark

Le Benchmark est une sorte d'étalon, c'est dans cette partie que l'IA cherche la meilleure librairie possible d'après les signaux envoyés dans la partie précédente.

Benchmark Delgod.png

Validation

Une fois que cette partie est terminée, nous pouvons passer à la partie Validation où nous pouvons importer un signal différent de ceux d'avant afin de choisir la meilleure librairie parmi celles trouvées dans la partie Benchmark.


Validation.png

Emulator

Enfin, nous pouvons tester en temps réel dans la partie Emulator si notre librairie a bien appris les signaux envoyés en entraînement.


EmulatorDelgod.png

Résultats

Nous avons filmé deux cas où l'IA reconnait les symboles C et I de la Langue des Signes Française (LSF) :


Comme vous pouvez le voir, certaines lettres sont mieux perçues que d'autres, cela est dû au fait que NanoEdge AI Studio est très sensible à la luminosité, mais aussi à la position de notre main par rapport au capteur.

Entraînement avec un script Python

Apprentissage avec des données sélectionnées

Dans un premier temps, nous avons créé une intelligence artificielle qui apprend grâce à des fichiers CSV que nous avons fait.

Nous avons choisi d'utiliser la librairie SciKit Learn et Random Forest pour développer notre IA.

La première étape dans notre script Python est de lire tous les CSV que nous lui fournissons (c'est-à-dire les CSV correspondant aux lettres A à K de la Langue des Signes Française). Ensuite, nous concaténons tous les CSV pour en faire une sorte de "mini" base de données. Cette base de données va être scindée en deux : 80 % des données sont faites pour l'apprentissage, et les 20 % restants sont pour le test.

Il faut savoir aussi que nous avons relié une lettre à un chiffre pour plus de facilité, nous avons donc :

Empty = 0, A = 1, B = 2, etc.....

Pour compliquer la tâche, nous avons décidé de mélanger toutes les données pour l'entraînement et le test, c'est-à-dire que le tableau de réponse ne sera pas sous la forme [0 0 0 ... 1 1 1 .... 2 ....] mais plutôt sous la forme : [7 11 6 6 0 10 5 ...]

Concernant la phase d'apprentissage, nous avons au total 4 variables :

  • X_train : données que la carte a envoyées qui sont destinées à l'entrainement
  • y_train : tableau de réponse (ce qu'on attend comme résultat)
  • X_test : données que la cartée a envoyées qui sont destinées au test
  • y_test : tableau de réponse que l'IA a déterminé

Enfin, nous avons mis le paramètre accuracy qui permet de déterminer le "score" de l'IA.

def random_forest_predict(X_train,y_train,X_test,y_test):
model = RandomForestClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test,y_pred)
print(y_pred)
print(f"Précision du modèle : {accuracy}")

AccuracyDelGod.png.png

Après le lancement du test, nous trouvons une accuracy de 1. Cette accuracy parfaite est sûrement due au fait que pour chaque lettre, nous étions dans les mêmes conditions (position, luminosité etc..)

Apprentissage avec des données en temps réel

Après avoir entraîné notre IA, nous allons la tester avec les données qui proviennent directement de la carte avec le capteur Time of Flight. Pour cela, nous avons utilisé la librairie Serial de python pour obtenir les données en temps réel. Comme les données sont sous forme d’une longue chaîne de caractère, nous avons dû séparer la chaîne en une liste, transformer les chaînes de caractères en entiers et enfin transformer la liste en un tableau.

Après avoir transformé les données dans le bon type, nous avons réutilisé la fonction predict pour que l’Intelligence Artificielle détecte quelle lettre nous avons fait.


def random_predict_serial(X_train, y_train,model):
    ser = serial.Serial()
    ser.port = PORT
    ser.baudrate = BAUDRATE
    ser.timeout = 0.2


    ser.open()
    print("Is serial open : ", ser.is_open)

    print(ser)

    lines = ser.readlines()
    for line in lines:
        # print(line)
        decoded_line = str(line, ENCODING)[:-2]
        print(decoded_line, " - ", len(decoded_line.split(",")))
        
        if(len(decoded_line.split(","))==64):
            test_line = decoded_line.split(",")
            test_line_int = [int(x) for x in test_line]
            test_array = np.array(test_line_int)
            test_array = test_array.reshape(1,-1)
            print("TYPE TEST :",type(test_array))
            prediction = model.predict(test_array)
            print("La prediction :" ,prediction)
    

    ser.close()
    print("Is serial open : ", ser.is_open)

Comme vous pouvez le voir, cette fonction redonne à la fin la prédiction du signal.

PredictionDelGod.png

Nous pouvons voir ici que le programme donne des prédictions que lorsque les données sont conformes et nous pouvons aussi remarquer que les prédictions restent les mêmes donc que l'IA est plutôt stable.

Dans ce cas-ci, la prédiction était "0" ce qui signifie, comme vous l'avez vu plus haut, à Empty.

Mise en place du modèle embarqué et des modèles hybrides

Séance du 04/12/2023

AU cours de cette première séance, nous avons pu prendre en main les outils nécessaires à la réalisation de notre projet. L’objectif était de construire un serveur Xen et de configurer la RaspberryPi.

  • Machine virtuelle sur Chassiron avec un accès internet IPv6.
  • Image de la Raspberry Pi pour connexion avec le serveur via WiFi : nous avons installé une image ** sur la rapsberry pi

Séance du 18/12/2023

Au cours de cette séance, nous allons tout d'abord

  • Développer un firmware embarqué pour collecter les données, avec STM32 Cube IDE et donc un programme permettant d'acquérir les données du capteur.
  • Mettre en œuvre un protocole de communication entre les quatre objets et la Raspberry Pi en garantissant que toutes les données sont bien reçues. Utilisation de la liaison série entre la carte et le capteur. Sur le port ttyACM0, avec un script python lancé de la rapsberry. Les données du capteur sont ensuite stockées dans un fichier .csv.

Séance du 19/12/2023

  • Mise en place d'un serveur web http sur le serveur delgodVM pour permettre la communication grâce à des requêtes http entre la raspberry pi et le serveur. Tout ceci est réalisé grâce des scripts python.

Séance du 20/12/2023

  • Transmission des datas récupérées par le capteur de la raspberry pi vers le serveur http.
  • Vérfication de la réception de ces données brutes
  • Début de l'apprentissage de notre AI, reconnaissance de la langue des signes