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.
Se glisser dans le code Arduino
- 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.
- 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.
- Le script Python de réception construit dans le premier didacticiel traitera les données entrant dans l’ordinateur.
- 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.
- 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.
- 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.
- 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
Un petit python
- 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.
- 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.
- 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.
- Le X et le y du programme jouent le rôle des données changeantes et des résultats de ce changement, respectivement.
- 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.
- Nous ne pouvons pas simplement prendre les valeurs telles qu'elles sont. Ils doivent subir une certaine normalisation.
- 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