Tech. industrie

Modèle ML et programmation Arduino pour l’apprentissage automatique Gants de frappe

Je suis titulaire d’un diplôme en technologie de l’information. J’ai récemment obtenu trois certificats en Tiny Machine Learning.

Ce tutoriel vous apprendra la programmation pour l’apprentissage automatique des gants de frappe

  • Le code requis pour obtenir des valeurs analogiques à partir d’un Arduino et en faire la moyenne.
  • Le code du modèle ML pour former votre réseau de neurones artificiels sur les données du premier tutoriel.
  • Tout cela pour que vos gants de dactylographie d’apprentissage automatique soient prêts à taper sur n’importe quelle surface dure.
ml-model-and-arduino-for-machine-learning-dactylographie-gants

Se glisser dans le code Arduino

  1. Les gants eux-mêmes doivent être étiquetés pour chaque main afin de les rendre intuitifs à utiliser. J’ai étiqueté le mien en rouge et vert.
  2. Veuillez garder à l’esprit que le code ci-dessous enverra les valeurs analogiques des capteurs flexibles attachés aux doigts via le Bluetooth Silver Mate de l’Arduino à l’ordinateur.
  3. Le script Python de réception construit dans le premier didacticiel traitera les données entrant dans l’ordinateur.
  4. Ensuite, stockez-le dans une liste de valeurs séparées par des virgules. Je crois qu’il y a six octets au total pour chaque valeur analogique.
  5. Le (A) signifiant un caractère analogique occupe 1 octet de mémoire les caractères de sens sont d’un octet. Ensuite, l’indicateur de numéro de capteur occupe quatre octets, car il s’agit d’un nombre entier.
  6. Ensuite, nous avons lowByte pour obtenir l’octet le moins significatif de la valeur analogique. Tant que les valeurs analogiques sont comprises entre 1 et 255, cela devrait être correct.
  7. En travaillant sur les gants, j’ai également remarqué que les valeurs analogiques étaient fausses, j’ai donc utilisé un filtre de moyenne pour les lisser en une valeur plus fiable.

Math.h est nécessaire pour lisser les données dans AverageFilter.

#include 

Dans la partie configuration du programme Arduino, nous avons défini le débit en bauds à 1 200

void setup() {
  Serial.begin(1200);
}

La moyenne prend 16 valeurs analogiques et en fait la moyenne seize fois au total. Après cela, nous divisons par le nombre de valeurs analogiques prises. Ensuite, la fonction renvoie une fraction. En faisant ces choses, il donne une représentation plus précise de la valeur analogique finale.

// Smooth the input to eliminate random values from machine learning model
int AverageFilter(int aNum)
{
  float AverageAnalogValue = 0;
  int MeasurementsToAverage = 16;
  for(int i = 0; i < MeasurementsToAverage; ++i)
  {
    AverageAnalogValue += analogRead(aNum);
    delay(1);
  }
  AverageAnalogValue /= MeasurementsToAverage;
  return TakeFraction(.10,AverageAnalogValue);
}
int TakeFraction(float FractionAmount,int AveragedAnalogValue)
{
  return round(AveragedAnalogValue * FractionAmount);
}

Le premier caractère imprimé (A) donne une certaine séparation entre les valeurs, et le deuxième numéro imprimé 5-9 après la valeur imprimée initiale. Cela nous permet d'identifier de quel capteur il s'agit.

La troisième valeur imprimée est la valeur analogique réelle du capteur flexible.

void loop() {
   // 6 bytes per sensor
   Serial.print("A");
   Serial.print("5");
   Serial.print(lowByte(AverageFilter(0))); // Read the local analog signal
   delay(5);
   Serial.print("A");
   Serial.print("6");
   Serial.print(lowByte(AverageFilter(1))); // Read the local analog signal
   delay(5);
   Serial.print("A");
   Serial.print("7");
   Serial.print(lowByte(AverageFilter(2))); // Read the local analog signal
   delay(5);
   Serial.print("A");
   Serial.print("8");
   Serial.print(lowByte(AverageFilter(3))); // Read the local analog signal
   delay(5);
   Serial.print("A");
   Serial.print("9");
   Serial.print(lowByte(AverageFilter(4)));
   Serial.print("\n");
 
}

Le tableau suivant montre à quoi ressembleront les données analogiques provenant du port de communication série.

Faites défiler pour continuer

A0{valeur entière}A1{valeur entière}A2{valeur entière}A3{valeur entière}A4{valeur entière}A5{valeur entière}A6{valeur entière}A7{valeur entière}A8{valeur entière}A9{valeur entière}

Un petit python

  1. Tout d'abord, nous importons certaines bibliothèques requises dans le projet. Certains d'entre eux ont été expliqués dans le premier didacticiel afin que nous ne les revenions pas.
  2. Je veux principalement me concentrer sur les bibliothèques TensorFlow, NumPy et Keras. Ceux-ci seront utilisés dans le modèle d'apprentissage automatique.
  3. Puisque nous voulons prédire quelle touche est enfoncée, sur la base des données des gants, certaines parties du script seront similaires au pythonserialmlgloves1.1.py que nous avons créé dans le premier tutoriel. Nous avons une variable dépendante et une variable indépendante.
  4. Le X et le y du programme jouent le rôle des données changeantes et des résultats de ce changement, respectivement.
  5. La touche du clavier enregistrée, dans ce cas, sera la valeur y ou cible. Le X sera toutes les valeurs analogiques que nous enregistrons à partir de notre script lorsque la touche est enfoncée.
  6. Nous ne pouvons pas simplement prendre les valeurs telles qu'elles sont. Ils doivent subir une certaine normalisation.
  7. Ils peuvent fausser le résultat final pour les valeurs significatives et mineures s'ils ne sont pas ramenés dans une plage spécifique. Et comme un ordinateur ne peut pas comprendre les lettres de l'alphabet, nous devons les convertir en utilisant OneHotEncoder.
# Tensorflow v1.5 was used in this project
import pandas as pd
import tensorflow as tf
import numpy as np
import keras
import serial
import threading
import re
from keras.models import model_from_json
import numpy
from numpy import array
from numpy import reshape
import queue

que = queue.Queue()
# How to connect gloves to fingers
# Start at pinky 1-5 green
# Start at thumb 1-5 red
BYTES_TO_READ = 23
#HC-06(RED)
ser0 = serial.Serial("COM9", 1200,bytesize=serial.EIGHTBITS,timeout=None, parity=serial.PARITY_NONE, rtscts=1)
#RN(GREEN)
#Changed stopbits to 1
ser1 = serial.Serial("COM10", 1200,bytesize=serial.EIGHTBITS,timeout=None, parity=serial.PARITY_NONE,rtscts=1)
dataset = pd.read_csv('directory')
X = dataset.iloc[:, 1:11].values
y = dataset.iloc[:, 0].values

# Taking care of missing data
from sklearn.preprocessing import Imputer
imputer = Imputer(missing_values = 'NaN', strategy = 'mean', axis = 0)
imputer = imputer.fit(X[:, 1:11])
X[:, 1:11] = imputer.transform(X[:, 1:11])

# dataset starts at 1 due to columns from openoffice formatting, I suppose
# Encoding categorical data
# Encoding the Dependent Variable
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder_y = LabelEncoder()
y = labelencoder_y.fit_transform(y)
# May need to only go this far in the encoding
onehotencoder = OneHotEncoder(sparse=False)
y = y.reshape(len(y), 1)
y = onehotencoder.fit_transform(y)

# Splitting the dataset into the Training set and Test set

from sklearn.model_selection import train_test_split
# Consider using another random_state seed when training the next time
# Changed random_state to 42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)


# Feature Scaling
from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)
# Importin keras dependencies
import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.models import model_from_json
# Initializing the ANN
classifier = Sequential()

Ce sont les couches du réseau de neurones. Nous n'avons qu'une seule couche d'entrée et une seule couche cachée. Le nombre de neurones pour cet ANN était arbitraire et peut être amélioré.

Cependant, la couche de sortie a besoin de 28 neurones pour les lettres codées de l'alphabet.

# Add 1st input hidden layer
classifier.add(Dense(17,kernel_initializer='uniform',activation='relu',input_dim = 10))

# Add second hidden layer
classifier.add(Dense(17,kernel_initializer='uniform',activation='relu'))

# Add the output layer
classifier.add(Dense(28,kernel_initializer='uniform',activation='softmax'))

Categorical_crossentropy a été utilisé car il s'agit d'un problème de classification.

classifier.compile(loss="categorical_crossentropy", optimizer="adam", metrics=['accuracy'])

# load json and create model
#json_file = open('model.json', 'r')
#loaded_model_json = json_file.read()
#json_file.close()
#loaded_model = model_from_json(loaded_model_json)
# load weights into new model

classifier.load_weights("directory")

print("Loaded model from disk")
# load json and create model
#json_file = open('directory', 'r')
#loaded_model_json = json_file.read()
#json_file.close()
#classifier = model_from_json(loaded_model_json)

#print("Loaded model from disk")

#classifier.fit(X_train,y_train,batch_size=10,nb_epoch=1000)

graph = tf.get_default_graph()

# serialize model to JSON
#model_json = classifier.to_json()
#with open("directory", "w") as json_file:
   # json_file.write(model_json)
# serialize weights to HDF5
#classifier.save_weights("directory")
#print("Saved model to disk")

Presque tout le code suivant est identique au script Python créé dans le didacticiel précédent. Mais notez l'utilisation d'un graphique en bas. Cela a été utilisé pour enregistrer l'état du réseau de neurones.

# Class to do the important procedures on the serial data
class serprocedure():
	# The following variables are class-level and are static, which means they will persist across class instances
	try:
		sensorlist
	except NameError:
		sensorlist = []
	# A list to store the missing data
	try:
		missingdata
	except NameError:
		missingdata = []
	# A counter for missing data
	try:
		mcounter
	except NameError:
		mcounter = 0
	try:
		times_counter
	except NameError:
		times_counter = 0

		# Use the __init__ constructor to take argument self and serdata
		# Each method should only do 1 thing
		def __init__(self,serdata,flag):
			self.serdata = serdata
			self.flag = flag
			self.serdatalooper(self.flag)
			# If it is the second thread with the second serial com, and missing counter less than 1, and sensorlist not greater than 10
			if self.flag == 1 and serprocedure.mcounter < 1 and len(serprocedure.sensorlist) == 10:
				# Tell the user that the program is performing further extraction
				print("Performing further extraction of data and exportation")
				# Perform further extraction
				self.furtherextraction(serprocedure.sensorlist)
				serprocedure.times_counter = serprocedure.times_counter + 1
				# It will only do this, if it's not the first serial COM with flag 0
			elif self.flag != 0:
				# Reset counter
				serprocedure.mcounter = 0
				# Clear the list so it doesn't build up
				serprocedure.sensorlist.clear()
				print("Throwing away partial or excess result.")
				que.put(False)
		
		# Method to extract the individual parts of the serial data
		def extractanalog(self,analognumber,flag):
			# Changed the decimal regexp to {1,2} quantifier
			found = re.search(r'(A' + str(analognumber) + '\d{1,2})',str(self.serdata))
			if found is not None:
				if found.group():
					# Create a list of data
					# sensorlist must be moved to the top to be a class-level variable
					#sensorlist = []
					serprocedure.sensorlist.append(str(found.group()))
					return
			else:
				serprocedure.mcounter += 1
				# It's getting stuck here
				return
			
				
		def furtherextraction(self,newlist):
			# A list to hold analog labels
			findanaloglabel = []
			# A list to hold analog values
			findanalogvalue = []
			z = 0
			#print("This is the list in furtherextraction:")
			#print(newlist)
			# Len counts 10 elements in the list but the index starts at 0
			while z < len(newlist):
				# These will have to be made into lists
				findanaloglabel.append(re.search(r'(A\d)',newlist[z]).group())
				# ?<= looks behind and only matches whats ahead
				# Changed the decimal regexp to {1,2} quantifier
				findanalogvalue.append(int(re.search(r'((?<=A\d{1})\d{1,2})',newlist[z]).group()))
				# Increment z
				z = z + 1
			# print the list findanalogvalue
			print(findanalogvalue)
				# Call the export method
			serprocedure.sensorlist.clear()
			# Return the analog values to main read_analog
			que.put(findanalogvalue)

		def serdatalooper(self,flag):
			if flag == 0:
				i = 0
				end = i + 4
			else:
				i = 5
				end = i + 4
			# Loop through the entire length of the list and extract the data
			while i <= end:
				self.extractanalog(i,"mainlist")
				# Increment the counter
				i = i + 1
				# Sort the list
			serprocedure.sensorlist.sort()
				
			#if len(serprocedure.missingdata) < 1:
				#q.put("There were no missing data")
				#return True
			#else:
				#q.put("There are " + str(len(serprocedure.missingdata)) + " of data missing from list")
				#return False

# read from serial port
def read_from_serial(serial,board,flag):
    #print("reading from {}: port {}".format(board, port))
    payload = b''
    
    bytes_count = 0
	# Changed the 1 in serial.read(1) to bytes_at_a_time
    #bytes_at_a_time = serial.in_waiting
	# Changed to  <= to make it count the last byte
    #while bytes_count <= BYTES_TO_READ:
        #read_bytes = serial.read(1)

	# sum number of bytes returned (not 2), you have set the timeout on serial port
	# see https://pythonhosted.org/pyserial/pyserial_api.html#serial.Serial.read
    # bytes_count = bytes_count + len(read_bytes)
	# This seems to be an improvement. Try to catch missing serial data.
    read_bytes = serial.read_until('\n',40)
    payload = payload + read_bytes

            # here you have the bytes, do your logic
            # Instantiate object from serprocedure class
    serprocobj = serprocedure(payload,flag)


def main():
	while True:
				# Pass in the function, serial, board, and key as agrguments
				# We'll pass in a flag to identify which board is being used
				t = threading.Thread(target=read_from_serial, args=(ser0,"HC-06(Red)",0))
				t1 = threading.Thread(target=read_from_serial, args=(ser1,"RN(Green)",1))
				# Start the threads
				t.start()
				t1.start()
				# Be careful this is blocking. Gets the missing data amount
				#print(q.get())
				# wait for all threads termination
				# The joins may be holding up the buffer flushes, if they are move them to the bottom
				#t.join()
				#t1.join()
				# Flush the serial input and output buffers
				ser0.reset_input_buffer()
				ser0.reset_output_buffer()
				ser1.reset_input_buffer()
				ser1.reset_output_buffer()
				t.join()
				t1.join()
				global graph
				with graph.as_default():
					analog_to_predict = que.get()
					if analog_to_predict != False:
						# The input had to be scaled in order for it to work somewhat better
						analog_to_predict = sc_X.transform(numpy.array([analog_to_predict]))
						new_pred = classifier.predict_classes(analog_to_predict)
						new_pred = labelencoder_y.inverse_transform(new_pred)
						print(new_pred)
					else:
						print("que.get() was False")



main()
	

Souhaitez-vous en savoir plus sur la fabrication de gants de frappe pour l'apprentissage automatique ?

Si vous êtes intéressé par ce processus, c'est une bonne idée de consulter mon premier tutoriel approfondi sur ce sujet, qui contient des informations supplémentaires :

Comment faire de meilleurs gants de frappe pour l'apprentissage automatique

Ce contenu est exact et fidèle au meilleur de la connaissance de l'auteur et ne vise pas à remplacer les conseils formels et individualisés d'un professionnel qualifié.

© 2021 Brock Lynch

A lire aussi :  De quel type de radio d'urgence avez-vous besoin en cas de catastrophe ?
Bouton retour en haut de la page