跳过主要内容
更新日期:

如何制作更好的机器学习打字手套

我是信息技术学位持有人。我最近获得了三个微型机器学习证书。

how-to-make-machine-learning-typing-gloves-that-are-better-than-mine

本教程将教你如何制作你自己的ML手套

本教程将带领您通过一个项目,我在我的业余时间使用一些电子组件和Python、Arduino和ML的知识。该项目重点是开拓一个非常小的和独特的领域,在这里我提到的三个学科交叉。正如你可能已经从标题中猜到的,这不是一个制作完美的机器学习打字手套的教程。

本教程将为您提供一些关于如何开始制作运行良好的产品的想法。这可能意味着您的模型的最终精度较低,或者手套只在某些时候起作用。

除了pythonserialmlgloves1.1还有其他三个模块,包括包含机器学习模型和Arduino代码的mlgloves1.1,所以本教程将分为三个教程系列。如果你愿意更详细地研究这个话题,那就不要再拖延了。

下面是制作手套的方法。

必需项目

10个挠性传感器(由吸管光电电阻器和电阻器制成)

2个Arduino Lilypads(或类似产品,但请记住输出引脚上的电压和模拟引脚的数量)

Arduino的2个蓝牙模块(我使用sparkfun.com的Silver Mate)

2节AAA电池

1台PC机(功能强大,可以在上面进行Arduino和Python编程)

1 PC蓝牙模块

1个电气胶带(可选)

各种颜色不同长度的电线

开始之前的一些注意事项

在我们开始之前,我想谈谈我在这个项目中遇到的一些问题,以及我本可以做得不同的事情。

当时对我来说并不明显的一件事是在我开始这个项目后才知道的。有一个机器学习的新领域刚刚起步,叫做微小ML。我第一次听说它是在边缘计算的名字下。这是一种将机器学习应用于小型功耗受限设备的新方法。

在TensorFlow 1中使用传统的ML方法的缺点。最后,推论必须在电脑上完成。如果手套变得普遍,那就意味着这个设备就不那么便携了。我本可以在Arduino Lilypads上做推断,所以我可以在Android或IOS上做一个应用程序,接收来自手套的ML推断。

手套的另一个缺点是,到最后,它们只能从键盘上可靠地识别大约10个字符。我把这归因于没有足够的训练数据来支持一个完整的功能模型。尽管如此,我还是不得不承认,在我花了很长时间在这个项目上之后,看到它仍然有效,我已经足够满足了。

我遇到的最后一个问题是,柔性传感器不够灵敏,无法捕捉到手指的所有动作,某些按键几乎没有阻力变化。把柔性传感器放在吸管中可以解决这个问题。

此外,在写这篇文章的时候,我被告知,框架是必要的。我不该用脏话。不,不是那个F字,而是失败。所以在这里,项目的问题将被称为缺点。

1.制作柔性传感器

这个项目需要使用挠性传感器在输入数据以训练机器学习模型时,记录手的手指运动。

我使用的柔性传感器是DIY的,它们通过光照射光敏电阻来工作。手指的弯曲改变了通过吸管进入光敏电阻的光线。

所需材料

10个光敏电阻

10个led(发光二极管)

10 470欧姆电阻
10 10 Kohm电阻

不同长度的铜线
绝缘胶带或热胶,以防止短路或掩盖光线
10塑料吸管
烙铁
升压转换器或9伏电池源
焊料

指令

步骤1:稻草必须剪成合适的长度。这个测量值可能会根据手指的长度而变化,但这可能是几英寸。小指、拇指和中指的大小可能相对不同,所以需要不同的吸管长度。

步骤2:LED应连接到挠性传感器电路的两侧。这将在相反的一端从你要放光电阻器的地方。确定你知道LED的哪一面是正的,哪一面是负的。为了记住这一点,我有一个类似于年度波尔卡多会议Nexus的助记符。阳极- >积极- >阴极- >负的。

步骤3:正极将连接到470欧姆电阻器。470欧姆电阻器和10K欧姆电阻器之间需要焊接一根导线。这样LED将接收大约19毫安的电流,而光刻胶获得相对较小的电流。该导线将连接到升压的9v电源或额外的电源rce。

步骤4:光敏电阻应放置在挠性传感器电路的另一侧(LED所在的位置)。光敏电阻的一端将连接到每个手指的一个模拟引脚(A0, A1, A2, A3, A4)。另一端将吸管的另一端连接到地面,向上到LED。

步骤5:(缺点)在这一点上,重要的是要记住,一旦你把所有组件放在一起,如果所有组件都在吸管内,flex传感器可能会工作得更好。

步骤6:柔性传感器的电源可以来自一个单独的9伏电池。但我建议尝试使用升压转换器来提高3.3伏或5 v到9v,以有足够的功率为led。

第七步:可能需要使用下拉电阻,但你必须研究这个。

2.进入项目分析

从收集数据开始是最有意义的,这将为项目的其余部分奠定基础。pythonserialmlgloves1 .py所做的实际上是以命令行界面的形式捕获数据。

训练手套的用户将输入一定次数的数据,以使模型有足够的数据学习关联,并在ML模型脚本中进行推断。我们将来自Arduino Lilypads(或类似Arduino设备)的所有数据通过串口进行通信。

每个蓝牙模块应该有它的com端口。然后提取和处理数据,然后导出到CSV。通过执行这个动作,它允许ML模型脚本执行推理(分类),以后的教程将对此进行解释。

下面的代码导入了一些东西,以便Python使用。我们现在导入了序列库,因为我们将处理Arduino-Lilypads之间的串行通信。我们还导入re-to-do正则表达式。在此之后,我们导入线程,因为不需要部分程序块。

Python中的阻塞方法会阻止程序流的继续。串行和MSVCRT方法是阻塞的。因此,使用程序的串行和关键捕获部分来记住这一点是至关重要的。

最后,我们导入msvcrt和CSV,用于键捕获和使用逗号分隔的值。

必需的库

进口系列进口再保险进口线程进口msvcrt导入csv

晨间连续剧

下面的代码打开了计算机和Arduinos之间的串行通信通道。变量ser0用于一只手,而ser1用于另一只手。

给出的参数用于设置将打开哪些com端口,例如波特率,以及将传输多少数据。

#因为每只手增加了模拟传感器,所以更改了。#设置为23字节是切断模拟传感器#9# HC-06(红色)ser0=串行。串行(“COM9”1200,bytesize=serial.EIGHTBITS,timeout=None,parity=serial.parity\u None,rtscts=1# RN(绿色)ser1 =系列。系列(“COM10”1200,bytesize=serial.EIGHTBITS,timeout=None,parity=serial.parity\u None,rtscts=1#类对串行数据做了重要的程序

有一些类

这是serprocedure类。它对进入计算机的数据进行大部分处理。

首先,我们声明类,然后尝试它们,看看属性是否已经存在。如果它们不存在,则声明/初始化它们。

#类对串行数据做了重要的程序类serprocedure():#下面的变量是类级的并且是静态的,这意味着它们将跨类实例持久化尝试:sensorlist除了NameError:sensorlist = []#一个存储丢失数据的列表尝试:缺失数据除了NameError:丢失数据=[]#丢失数据的计数器尝试:mcounter除了NameError:mcounter = 0关于serdatalooper是否在第一次运行时获取所有数据,请设置为true或false尝试:First_Try除了NameError:First_Try = False尝试:times_counter除了NameError:times_counter = 0

建设性的

然后定义init构造函数。我们使用标志来控制程序的流,并决定是否执行进一步的提取或丢弃结果。

#使用_init__构造函数获取参数self和serdata#每个方法只做一件事#使用_init__构造函数获取参数self和serdata#每个方法只做一件事def __init__ (自我serdata,国旗,键):自我.serdata=serdata自我.flag =国旗自我.serdatalooper (自我(国旗)自我关键。key =#如果它是第二个串行com的第二个线程,并且缺少的计数器小于1,并且传感器列表不大于10如果自我.flag = =1serprocedure。mcounter <1len (serprocedure.sensorlist) = =10#告诉用户程序正在执行进一步的提取打印“执行进一步的数据提取和导出”#执行进一步提取自我.furtherextraction (serprocedure.sensorlist)serprocedure。times_counter= serprocedure.times_counter +1#表示用户输入了多少次打印“你输入”+ str (serprocedure.times_counter) +“时间(s)”".你刚打完这封信。”+str(自我。key))elif自我.flag ! =0#复位计数器serprocedure。mcounter =0#清除列表,使其不会累积serprocedure.sensorlist.clear ()打印“扔掉部分结果。”#检查是否有丢失的数据,是否有循环。否则正常循环数据。

做你的研究

如果您熟悉正则表达式,那么您将熟悉re.Search。re.Search将搜索模拟指示器和一些数字的数据。我们将执行验证以检查找到的值是否不等于零。

在此之后,如果我们找到一个组,那么它会附加到传感器列表中。如果我们看到这个,那么mcounter也会增加1。

方法提取串行数据的各个部分def extractanalog (自我analognumber标志):#将十进制regexp更改为{1,2}量词发现= re.search (r“(”+ str (analognumber) +“\ d{1,2})”str (自我.serdata))如果found不是None:如果found.group ():#创建数据列表# sensorlist必须被移到顶部作为一个类级别变量#传感器列表=[]serprocedure.sensorlist.append(str(found.group()))返回其他的serprocedure.mcounter+=1#它被卡在这里了返回

要进一步

进一步提取对来自ML手套的数据进行进一步的数据处理和提取。

def furtherextraction (自我,新列表):#保存模拟标签的列表findanaloglabel = []#用于保存模拟值的列表findanalogvalue = []z=0打印“这是进一步提取的列表:”打印(newlist)# Len计数列表中10个元素,但索引从0开始z < len (newlist):#这些必须被列成清单findanaloglabel.append (re.search (r“(A \ d)”, newlist [z]) .group ())# ?<=只匹配前面的内容#将十进制regexp更改为{1,2}量词findanalogvalue.append(重新搜索(r”((? < = A \ d {1}) \ d{1,2})”, newlist [z]) .group ())#增量zZ = Z +1#调用导出方法自我.exporttocsv (findanaloglabel findanalogvalue)#导出到excel表格

但分开

Exporttocsv是一种方法,用于从进一步提取和提取模拟中获取所有收集的数据,并将它们放入整洁的单个单元中。

def exporttocsv (自我、标签值):#将键值插入列表值。插入(0自我。key)敞开(“目录”“a”换行符=''作为CSV值:fhandle=csv.writer(csvalues)#将该行导出为csv文件fhandle.writerow(值)打印“已完成将值导出到csv”如果自我.flag = =1serprocedure.sensorlist.clear ()

循环越大,程序员就越厉害

Serdatalooper循环每个flex传感器的值。如果标志设置为0,则为一只手,否则为另一只手的数据。

def serdatalooper (自我、旗):如果标志= =0我=0结束=i+4其他的我=5结束=i+4#循环遍历整个列表长度并提取数据我< =结束:自我.extractanalog(我“mainlist”#增加计数器I = I +1#排序列表serprocedure.sensorlist.sort ()#if len(serprocess .missingdata) < 1:#问。put(“没有丢失的数据”)#还真其他:#问。put("There are " + str(len(serprocess .missingdata)) + " of data missing from list")#返回错误

3.另一个例子,谢谢!

当我这样做的时候,我可能会收集到更多的数据。

如果你想要改进手套的设计,设定一个每个角色拥有1000个例子的数据集可能是个不错的目标。获得1000个例子并不意味着一个人必须费力地输入每个字符。

您可以收取费用或免费从其他用户收集自定义数据集。它还可以通过被动地收集数据,让用户按照常规的打字体验来进行操作,而不是像往常一样每个字母都要打x次。

Read_from_serial读取字节直到“\n”。我计算这个值为~30,但是代码的这个特定区域可以改进。我想发生的是,从手套中输入的每个整数和字符都有10个字符,因此总共有30个字节。

因为字符是1字节,低字节用于来自Arduinos的整数,使它们也是1字节。然后它被附加到包含b "的有效载荷中。

然后从serprocedure类实例化对象serprocobj。

#从串行端口读取def read_from_serial(串行、董事会、关键、旗):#打印(“读取{}:端口{}”。格式(板,端口))有效载荷=b”# bytes_count = 0#将BYTES_TO_READ改为inWaiting实现# inwaitingbytes = serial.in_waiting ()#更改为<=以使其计数最后一个字节#while bytes_count <= inwaitingbytes:# read_bytes = serial.read (1)# sum返回的字节数(不是2),你已经设置了串口的超时时间#见https://pythonhosted.org/pyserial/pyserial_api.html # serial.Serial.read#bytes_count = bytes_count + len(read_bytes)read_bytes = serial.read_until (“\ n”30Payload = Payload + read_bytes这里你有字节,做你的逻辑#从serprocedure类实例化对象serprocobj = serprocedure(载荷、国旗、关键)#如果属性THREE_TIMES_PROPERTY大于#打印(“读取  {}: [{}]". 格式(板、负载)返回def计数器():如果“问”不在柜台上counter.cnt =0其他的counter.cnt+=1返回counter.cnt

4.最终线程

main中涉及的重要思想可能不是很明显,它们是密钥获取和线程。密钥获取由key=msvcrt.getch()完成。

还记得我之前跟你说过某些东西被屏蔽了吗?变量t, t1,和t.start(), t1.start()可以防止不同的串行调用阻塞,从而促进代码流的正常进程。Reset_input_buffer是另一个重要的方法。

我艰难地发现,如果没有这个,一些数据在进程运行后仍然留在缓冲区中。

def main ():真正的#出于某种原因,它每隔一次就更新一次这是1 2 3的响应如果“rsp”不在主目录中。\u dict\u#询问用户想要进行哪些培训打印“有三个选择。按1训练字母键a-z,按2训练空格键,按3训练主排休息位置。”主要。负责=输入(“你想做什么?”#根据给定类型的答案如果main.rsp==' 1 '如果“问”不在主目录中。\u dict\umain.cnt =0#请求用户输入每个字母20次,以便机器学习数据集进行训练主要。准备好=输入(“请在键盘上每个字母打20次。准备好后输入y,然后回车。”艾利夫·梅因,准备好了吗==“y”main.cnt+=1关键= msvcrt.getch ()如果钥匙计数器()< =520#将function、serial、board和key作为参数传入#我们将通过一个旗帜,以确定哪个板正在使用线程(目标=从串行读取,参数=(ser0,“HC-06(红色)”,key.decode(“ASCII”),0))t1=线程。线程(目标=从串行读取,参数=(ser1,“RN(绿色),key.decode(“ASCII”),1))#启动线程t.start ()t1.start()#小心这是阻塞。获取丢失的数据量#打印(q.get ())#等待所有线程终止#连接可能会阻碍缓冲区刷新,如果他们移动到底部刷新串行输入和输出缓冲区ser0.reset_input_buffer ()ser0.reset_输出_缓冲区()ser1.reset_input_buffer()ser1.reset_output_buffer ()t.join ()t1.join()其他的打破elif主要。负责= =' 2 '如果“问”不在主目录中。\u dict\umain.cnt =0#请求记录homerow条目的权限主要。准备好=输入(“请将手指放在正常位置,并打空格20次。”输入j,然后回车。”艾利夫·梅因,准备好了吗==“j”main.cnt+=1#这次我们不需要记录钥匙。如果计数器()< =20#将function、serial、board和key作为参数传入#我们将通过一个旗帜,以确定哪个板正在使用线程(目标=从串行读取,参数=(ser0,“HC-06(红色)”“homerow”0))t1=线程。线程(目标=从串行读取,参数=(ser1,“RN(绿色)“homerow”1))#启动线程t.start ()t1.start()#小心这是阻塞。获取丢失的数据量#打印(q.get ())#等待所有线程终止#连接可能会阻碍缓冲区刷新,如果他们移动到底部刷新串行输入和输出缓冲区ser0.reset_input_buffer ()ser0.reset_输出_缓冲区()ser1.reset_input_buffer()ser1.reset_output_buffer ()t.join ()t1.join()其他的打破elif主要。负责= =“3”如果“问”不在主目录中。\u dict\umain.cnt =0#请求用户记录空间20次主要。准备好=输入(这一步需要你输入空格键20次。首先,按y键并按enter键。艾利夫·梅因,准备好了吗==“y”main.cnt+=1关键= msvcrt.getch ()#若就绪等于y(是),且计数器小于或等于20,否则中断如果钥匙计数器()< =20#将function、serial、board和key作为参数传入#我们将通过一个旗帜,以确定哪个板正在使用线程(目标=从串行读取,参数=(ser0,“HC-06(红色)”“空间”0))t1=线程。线程(目标=从串行读取,参数=(ser1,“RN(绿色)“空间”1))#启动线程t.start ()t1.start()#小心这是阻塞。获取丢失的数据量#打印(q.get ())#等待所有线程终止#连接可能阻碍了缓冲区刷新,如果是,请将它们移到底部刷新串行输入和输出缓冲区ser0.reset_input_buffer ()ser0.reset_输出_缓冲区()ser1.reset_input_buffer()ser1.reset_output_buffer ()t.join ()t1.join()其他的打破打印“感谢您使用ML手套项目进行培训”#在程序附近添加了序列号ser0.close()ser1.close ()main ()

谢谢你!

非常感谢你阅读本教程!你可以在布罗克氏Gists.关于这个主题的进一步深入信息,您可能想访问我的第二个教程,关于如何用Arduino编程机器学习打字手套。你可以在这里找到:ML手套的Arduino和机器学习编程

就作者所知,这篇文章是准确而真实的。内容仅用于信息或娱乐目的,不替代个人咨询或商业、金融、法律或技术问题的专业意见。

©2021 Brock Lynch

相关文章

Baidu