Tech. industrie

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

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 à fabriquer vos propres gants ML

Ce didacticiel vous guidera à travers un projet que j’ai réalisé pendant mon temps libre en utilisant des composants électroniques que j’avais traînés et une connaissance de Python, Arduino et ML. Le projet se concentre sur la création d’un domaine très niche et unique où les trois anciennes disciplines que j’ai mentionnées se croisent. Comme vous l’avez peut-être deviné d’après le titre, il ne s’agit pas d’un didacticiel sur la fabrication des gants de frappe parfaits pour l’apprentissage automatique.

Ce tutoriel vous donnera quelques idées sur la façon de commencer à créer quelque chose qui fonctionne plutôt bien. Cela peut signifier que la précision finale de votre modèle est faible ou que les gants ne fonctionnent que de temps en temps.

Il existe également trois autres modules en plus de pythonserialmlgloves1.1, y compris le mlgloves1.1 contenant le modèle d’apprentissage automatique et le code Arduino, de sorte que ce didacticiel sera séparé en une série de trois didacticiels. Si vous êtes prêt à approfondir ce sujet plus en détail, alors sans plus tarder.

Voici comment fabriquer les gants.

Noter:

Lorsque j’ai réalisé ce projet, je n’ai pas mis les composants du capteur flexible à l’intérieur de la paille. Au lieu de cela, j’ai utilisé de la colle chaude pour isoler l’extérieur du circuit du capteur flexible tandis que les pièces chevauchaient les deux côtés de la paille. J’ai utilisé de petits élastiques pour tenir les gants sur les mains, et ceux-ci ont bien fonctionné mais ont commencé à couper la circulation vers mes doigts après un certain temps. Il serait probablement préférable d’utiliser des bandes velcro. Pour que les gants ML fonctionnent mieux, vous pouvez également incorporer un mouvement latéral.

Articles requis

10 Flex Sensors (fabriqués à partir de pailles photo-résistances et résistances)

2 Arduino Lilypads (ou produit similaire, mais gardez à l’esprit la tension sur les broches de sortie et le nombre de broches analogiques)

2 modules Bluetooth pour Arduino (j’ai utilisé le Silver Mate de sparkfun.com)

2 piles AAA

1 PC (suffisamment puissant pour faire la programmation Arduino et Python)

1 module Bluetooth PC

1 ruban isolant (facultatif)

Différents fils de couleur de différentes longueurs

Quelques mots d’avertissement avant de commencer

Avant de commencer, j’aimerais parler de certains problèmes que j’ai rencontrés au cours de ce projet et de choses que j’aurais pu faire différemment.

Faites défiler pour continuer

La principale chose qui n’était pas évidente pour moi à l’époque était quelque chose que je n’apprendrais que plus tard après avoir commencé ce projet. Il existe un nouveau domaine d’apprentissage automatique qui commence tout juste à décoller appelé Tiny ML. J’ai appris cela pour la première fois sous le nom d’edge computing. C’est une toute nouvelle façon d’utiliser l’apprentissage automatique sur de petits appareils à puissance limitée.

L’inconvénient d’utiliser la méthode traditionnelle de ML dans quelque chose comme TensorFlow 1.x était qu’en fin de compte, l’inférence devait être faite sur l’ordinateur. Si les gants étaient devenus courants, cela aurait signifié que l’appareil n’aurait pas été très portable. J’aurais plutôt fait l’inférence sur les Arduino Lilypads, donc j’aurais pu créer une application sur Android ou IOS qui recevrait l’inférence ML des gants.

Un autre inconvénient des gants est qu’au final, ils n’ont pu reconnaître de manière fiable qu’une dizaine de caractères du clavier. J’ai attribué cela au fait de ne pas avoir suffisamment de données d’entraînement pour prendre en charge un modèle pleinement fonctionnel. Pourtant, je dois admettre que le voir fonctionner après les longues heures que j’ai passées sur le projet était assez satisfaisant.

Le dernier problème que j’ai rencontré était que les capteurs flexibles n’étaient pas assez sensibles pour capter tous les mouvements des doigts, car pratiquement aucune résistance ne change pour certaines touches tapées. Mettre les capteurs flexibles contenus dans les pailles pourrait remédier à cela.

Aussi, lors de la rédaction de cet article, on m’a avisé que le cadrage de celui-ci était nécessaire. Que je ne devrais pas utiliser le mot F. Non, pas ce mot F, mais le mot échec. Donc, ici, les problèmes avec le projet seront appelés inconvénients.

comment-faire-des-gants-de-dactylographie-machine-learning-qui-sont-meilleurs-que-les-miens

1. Fabrication des capteurs flexibles

Ce projet nécessite l’utilisation de capteurs flexibles pour enregistrer les mouvements des doigts des mains lors de la saisie des données pour former le modèle d’apprentissage automatique.

A lire aussi :  Types d'emplois en ingénierie - InformatiKKa

Les capteurs flexibles que j’ai utilisés étaient du bricolage, ils fonctionnent grâce à une lumière projetée contre une photorésistance. La flexion des doigts modifie la lumière laissée à travers la paille sur la photorésistance.

Matériaux nécessaires

10 photorésistances

10 LED (diode électroluminescente)

10 résistances de 470 ohms
10 résistances de 10 Kohms

Fil de cuivre de différentes longueurs
Ruban isolant ou colle chaude pour éviter les courts-circuits ou dissimuler la lumière
10 pailles en plastique
Fer à souder
Boost Converter ou source de cellule 9 volts
Souder

Des instructions

Étape 1: La paille devra être coupée à la bonne longueur. Cette mesure peut varier en fonction de la longueur des doigts, mais ce sera probablement plusieurs pouces. L’auriculaire, le pouce et le majeur seront probablement de tailles relativement différentes, il faudra donc des longueurs de paille différentes.

Étape 2: La LED doit être connectée aux deux côtés du circuit du capteur flexible. Ce sera à l’extrémité opposée d’où vous allez mettre la photorésistance. Assurez-vous de savoir quel côté de la LED est positif et quel côté est négatif. Pour m’en souvenir, j’ai un mnémonique qui ressemble à Annual Polkadot Conference Nexus. Anode-> Positif-> Cathode-> Négatif.

Étape 3: Le côté positif sera connecté à une résistance de 470 ohms. Entre la résistance de 470 ohms et une résistance de 10K ohms, un fil doit être soudé. C’est ainsi que la LED recevra environ 19 milliampères de courant, tandis que la photorésistance reçoit une quantité de courant relativement faible. Ce fil ira soit à une source 9v boostée, soit à une source d’alimentation supplémentaire.

Étape 4: La photorésistance doit être placée sur le côté opposé du circuit du capteur flexible (où se trouve la LED). Une extrémité de la photorésistance sera connectée à l’une des broches analogiques (A0, A1, A2, A3, A4) pour chaque doigt. L’autre extrémité sera reliée au sol et de l’autre côté de la paille, jusqu’à la LED.

Étape 5 : (inconvénient) Il est important de se rappeler à ce stade qu’une fois que vous avez assemblé tous les composants, le capteur flexible fonctionnera probablement mieux si tous les composants sont dans la paille.

Étape 6 : La source d’alimentation du capteur flexible peut provenir d’une cellule de 9 volts séparée. Mais je suggère d’essayer d’utiliser un convertisseur boost pour booster 3,3 volts ou 5 v à 9 v, pour avoir assez de puissance pour les LED.

Étape 7 : Les résistances pull-down devront peut-être être utilisées, mais vous devrez vous pencher là-dessus.

Noter:

Bien que le schéma ne montre pas les modules Bluetooth ou le convertisseur élévateur, ils devront être connectés. Bien que le convertisseur boost soit facultatif, il est recommandé. Les modules Bluetooth sont relativement faciles à connecter s’ils sont livrés avec des en-têtes déjà soudés.

2. Entrer dans l’analyse du projet

Il est plus logique de commencer par la collecte de données, qui jettera les bases du reste du projet. Ce que fait essentiellement pyhthonserialmlgloves1.1.py, c’est capturer des données sous la forme d’une interface de ligne de commande.

L’utilisateur formant les gants taperait un certain nombre de fois pour avoir suffisamment de données pour que le modèle apprenne les associations pour faire des inférences dans le script du modèle ML. Nous communiquons toutes les données des Arduino Lilypads (ou des appareils Arduino similaires) via les ports de communication série.

Chaque module Bluetooth doit avoir son port com. Les données sont ensuite extraites et traitées, puis exportées vers un CSV. En effectuant cette action, cela permet au script de modèle ML, qu’un futur tutoriel expliquera, d’effectuer une inférence (classification).

Le code suivant importe quelques éléments pour que Python puisse les utiliser. Nous importons maintenant la série de la bibliothèque, car nous allons travailler avec la communication série vers et depuis les Lilypads Arduino. Nous importons également re pour faire des expressions régulières. Après cela, nous importons le threading car il est essentiel de ne pas avoir de parties du bloc de programme.

Les méthodes de blocage en Python empêchent le déroulement du programme de continuer. les méthodes série et msvcrt bloquent. Par conséquent, il est essentiel d’utiliser les parties de capture en série et critiques du programme pour garder cela à l’esprit.

Enfin, nous importons msvcrt et CSV pour la capture de clé et le travail avec des valeurs séparées par des virgules.

Bibliothèques requises

import serial
import re
import threading
import msvcrt
import csv

Série du matin

Le code trouvé ci-dessous ouvre des canaux de communication série entre l’ordinateur et les Arduinos. La variable ser0 est pour une main tandis que ser1 est pour l’autre.

Les arguments donnés permettent de définir les ports com qui seront ouverts, des éléments tels que le débit en bauds et la quantité de données qui seront transférées.

# This was changed to account for the newly added analog sensors on each hand.
# Setting it to 23 bytes was cutting off the analog sensor #9
#HC-06(RED)
ser0 = serial.Serial("COM9", 1200,bytesize=serial.EIGHTBITS,timeout=None, parity=serial.PARITY_NONE, rtscts=1)
#RN(GREEN)
ser1 = serial.Serial("COM10", 1200,bytesize=serial.EIGHTBITS,timeout=None, parity=serial.PARITY_NONE, rtscts=1)
# Class to do the important procedures on the serial data

Avoir de la classe

C’est la serprocédure de la classe. Il effectue la majeure partie du traitement des données entrant dans l’ordinateur.

A lire aussi :  3 raisons pour lesquelles les fabricants de smartphones ne fabriquent pas leurs propres puces

D’abord, nous déclarons notre classe, puis nous les essayons pour voir si les propriétés n’existent pas déjà. S’ils n’existent pas, nous les déclarons/initialisons.

# 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
	# Boolean to store true false as to whether serdatalooper gets all data on the first run
	try:
		First_Try
	except NameError:
		First_Try = False
	try:
		times_counter
	except NameError:
		times_counter = 0

Être constructif

Le constructeur init est ensuite défini. Nous utilisons des drapeaux pour contrôler le déroulement du programme et décider d’effectuer ou non une extraction supplémentaire ou de jeter le résultat.

		# Use the __init__ constructor to take argument self and serdata
		# Each method should only do 1 thing
		# Use the __init__ constructor to take argument self and serdata
		# Each method should only do 1 thing
		def __init__(self,serdata,flag,key):
			self.serdata = serdata
			self.flag = flag
			self.serdatalooper(self.flag)
			self.key = key
			# 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
				# This tells how many times the user has typed 
				print("You've typed " + str(serprocedure.times_counter) + " Time(s)" ". You just typed the letter " + str(self.key))
			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 result.")
			# Check if there is missing data, if there is loop through that. Otherwise loop through the data normally.

Faire vos recherches

Si vous êtes familier avec les expressions régulières, vous serez à l'aise avec re.Search. Re.search recherche dans les données l'indicateur analogique et certains chiffres. Nous effectuons une validation pour vérifier si trouvé n'est pas égal à Aucun.

Après cela, si nous trouvons un groupe, il s'ajoute à la liste des capteurs. Si nous voyons cela, le mcounter est également incrémenté de 1.

		# 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

Aller plus loin

Une extraction supplémentaire effectue un traitement et une extraction supplémentaires des données sur les données provenant des gants ML.

		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(re.search(r'((?<=A\d{1})\d{1,2})',newlist[z]).group())
				# Increment z
				z = z + 1
				# Call the export method
			self.exporttocsv(findanaloglabel,findanalogvalue)
		# Export to excel form

Ensemble mais à part

Exporttocsv est une méthode utilisée pour prendre toutes les données recueillies à partir d'une extraction supplémentaire et le code dans l'analogue d'extrait et les place dans des cellules individuelles soignées.

		def exporttocsv(self,labels,values):
			# Insert key value into list
			values.insert(0,self.key)
			with open('directory','a',newline='') as csvalues:
				fhandle = csv.writer(csvalues)
				# Export the row to csv file
				fhandle.writerow(values)
				print("Done exporting values to csv")
				if self.flag == 1:
					serprocedure.sensorlist.clear()

Plus la boucle est grande, plus le programmeur est grand

Serdatalooper parcourt les valeurs de chaque capteur flexible. Si le drapeau est défini sur 0, il s'agit d'une main, sinon, ce sont les données de l'autre main.

		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

3. Un autre exemple, s'il vous plaît !

Lorsque j'ai fait cela, j'aurais probablement pu recueillir plus de données que je n'en ai fait.

Si vous essayez d'améliorer la conception des gants, viser un ensemble de données avec 1000 exemples par personnage peut être un bon objectif. Obtenir 1000 exemples ne signifie pas nécessairement qu'une personne doit taper laborieusement chaque caractère.

Vous pouvez collecter un ensemble de données personnalisé auprès d'autres utilisateurs moyennant des frais ou gratuitement. Il aurait également pu être rendu plus convivial en collectant passivement des données au fur et à mesure que l'utilisateur poursuit son expérience de frappe régulière, au lieu d'avoir à taper chaque lettre x fois.

Read_from_serial lit les octets jusqu'à "\n". J'ai calculé que c'était ~ 30, mais ce domaine particulier du code pourrait s'améliorer. Ce que j'ai pensé qu'il se passait, c'est qu'il y avait dix caractères pour chaque entier et caractère provenant des gants, ce qui équivaut à 30 octets.

A lire aussi :  15 avantages du GPS - InformatiKKa

Étant donné que les caractères sont de 1 octet et que l'octet de poids faible est utilisé sur les nombres entiers provenant des Arduinos, ce qui en fait également 1 octet. Il est ensuite ajouté à la charge utile contenant b''.

Ensuite, l'objet serprocobj est instancié à partir de la classe serprocedure.

# read from serial port
def read_from_serial(serial,board,key,flag):
    #print("reading from {}: port {}".format(board, port))
    payload = b''
    
    #bytes_count = 0

	# CHANGED BYTES_TO_READ to inWaiting implementation
    #inwaitingbytes = serial.in_waiting()
	# Changed to  <= to make it count the last byte
    #while bytes_count <= inwaitingbytes:
        #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)
    read_bytes = serial.read_until("\n",30)
    payload = payload + read_bytes

            # here you have the bytes, do your logic
            # Instantiate object from serprocedure class
    serprocobj = serprocedure(payload,flag,key)     
   
	# If the property THREE_TIMES_PROPERTY is greater than
    #print("READ from {}: [{}]".format(board,payload))
    return

def counter():
	if 'cnt' not in counter.__dict__:
		counter.cnt = 0
	else:
		counter.cnt += 1
	return counter.cnt

4. Le fil final

Les idées importantes impliquées dans main qui peuvent ne pas être immédiatement évidentes sont la récupération des clés et les threads. La récupération de la clé est effectuée par key = msvcrt.getch().

Vous vous souvenez quand je vous ai parlé plus tôt de certaines choses bloquantes ? Les variables t, t1, et t.start(), t1.start() empêchent les différents appels en série de se bloquer, facilitant ainsi le déroulement normal du flux de code. reset_input_buffer est une autre méthode importante.

J'ai découvert à la dure que sans cela, certaines données restaient dans les tampons après l'exécution du processus.

def main():
	while True:
		# For some reason it's only updating every other go
		# This will be the response of 1, 2, or 3
		if 'rsp' not in main.__dict__:
			# Ask what the user wants to train on 
			print("There are three options. Press 1 to train on alphabetic keys a-z, press 2 to train on space key, and press 3 to train on resting home-row position.")
			main.rsp = input("What would you like to do? ")
		# Depending on the answer received train on the given type
		if main.rsp == '1':
			if 'cnt' not in main.__dict__:
				main.cnt = 0
				# Request that the user type each letter 20 times for the machine learning dataset to train on
				main.ready = input("Please type each letter of the alphabet on the keyboard 20 times. Type y when ready and hit enter. ")
			elif main.ready == 'y':
				main.cnt += 1
				key = msvcrt.getch()
				if key and counter() <= 520:
					# 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)",key.decode('ASCII'),0))
					t1 = threading.Thread(target=read_from_serial, args=(ser1,"RN(Green)",key.decode('ASCII'),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
					# 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()
				else:
					break
		elif main.rsp == '2':
			if 'cnt' not in main.__dict__:
				main.cnt = 0
				# Request permission to record homerow entries
				main.ready = input("Please place fingers in homerow positions and type space 20 times. Type j and hit enter. ")
			elif main.ready == 'j':
				main.cnt += 1
				# We don't need to record the key this time.
				if counter() <= 20:
					# 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)","homerow",0))
					t1 = threading.Thread(target=read_from_serial, args=(ser1,"RN(Green)","homerow",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
					# 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()
				else:
					break
		elif main.rsp == '3':
			if 'cnt' not in main.__dict__:
				main.cnt = 0
				# Request the user record space 20 times
				main.ready = input("This step requires you to type the space key 20 times. To begin, press the y key and hit enter")
			elif main.ready == 'y':
				main.cnt += 1
				key = msvcrt.getch()
				# If ready equals y(yes) and counter is less than or equal to 20, else break
				if key and counter() <= 20:
					# 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)","space",0))
					t1 = threading.Thread(target=read_from_serial, args=(ser1,"RN(Green)","space",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
					# 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()
				else:
					break
	print("Thank you for training using this ML Gloves program")
	# Added serial close to the program
	ser0.close()
	ser1.close()

main()

Merci!

Merci beaucoup d'avoir lu ce tutoriel ! Vous pouvez voir le projet terminé sur Brock's Gists. Pour plus d'informations sur ce sujet, vous pouvez consulter mon deuxième tutoriel sur la programmation de gants de frappe en apprentissage automatique avec Arduino. Il peut être trouvé ici: Programmation Arduino et Machine Learning pour les gants ML

Cet article est exact et fidèle au meilleur de la connaissance de l'auteur. Le contenu est uniquement à des fins d'information ou de divertissement et ne remplace pas un conseil personnel ou un conseil professionnel en matière commerciale, financière, juridique ou technique.

© 2021 Brock Lynch

Bouton retour en haut de la page