我不知道为什么会有这样的想法,我自己既不失聪,也不失语,而且周围也没有这样的人,我甚至连语音助手也没有。究其原因,或许是因为有很多文章都在介绍层出不穷的语音助手,也可能是因为各大公司都在争相成为您青睐的智能家居语音助手,又或许只是因为我越来越多地在朋友家中的桌台上看到这些设备。这个问题在我的脑海中一直萦绕不去,我知道这件事势在必行。
于是,最后有了这个项目。这是一个概念验证项目,我买了一个 Amazon Echo,让它对手语作出反应,更准确地说是美国手语 (ASL),因为和口语一样,手语的种类也有很多。
原本我可以只发布代码,不过,我觉得很多机器学习项目都缺少视觉元素,让人们很难将它们联系起来理解,所以后来还是决定发布一段关于系统演示的视频。同时,我也希望这个方法能够将人们关注的焦点从项目的科技元素转向人类元素,即这里介绍的并非底层技术,而是此类技术赋予我们人类的能力。
既然视频已经公开,下面就通过这篇博文介绍一下底层技术,以及如何使用 TensorFlow.js 构建这一系统。您也可以播放实时演示。我将其整合在了一起,以便您可以使用自己的词集和符号/手势组合来训练。是否要在身边准备一个可以响应您要求的 Echo,完全由您自选。
早期研究
在早期,我就非常明确我想要为此实验整合哪些更广泛的系统组成部分。我需要的组件如下:
一个神经网络,用以解释手势(即将手势视频转换为文本)。
一个文字转语音系统,用于将解释过的手势讲给 Alexa
一个语音转文字系统,用于将 Alexa 的回应转录给用户使用
一台运行该系统的设备(笔记本电脑/平板电脑)和用于互动的 Echo
可以将这些组件整合在一起的界面
早期,我的大部分时间可能都用在了确定哪个神经网络架构最适合此实验上面。我想到下面几个选择:
1) 由于手势兼具可视性和时效性,因此我的直觉是将 CNN 与 RNN 结合在一起,其中最后一个卷积层的输出(分类前)作为序列馈送到 RNN 中。后来,我为此找到一个技术术语,即长期递归卷积网络 (LRCN)。
2) 使用 3D 卷积网络,以三维方式应用卷积,其中前两个维度是图像,第三个维度是时间。但是,这些网络需要大量存储设备,而我希望可以在我用了 7 年的老 Macbook Pro 上进行训练。
3) 训练 CNN 时,我没有使用视频流中的单个帧,而是只用了可以表示两个连续帧之间视运动模式的光流表征。我的想法是它会对这种动作进行编码,产生更通用的手语模型。
4) 使用双流 CNN,其中空间流为单个帧 (RGB),时间流会使用光流表征。
在做进一步研究时,我发现了一些论文,其中至少使用了上述部分视频行为识别方法(在UFC101 数据集上最常见)。但我很快发现,不仅我的计算能力有限,我从头开始解密和实施这些论文的能力也很有限,之后几个月,我的研究时断时续,经常因为其他项目缠身而不得不丢下该项目,我的输出结果一直乏善可陈。
最终,我采用了迥异的方法。
输入 TensorFlow.js:
TensorFlow.js 团队一直在推出基于浏览器的有趣小实验,帮助人们熟悉机器学习的概念,同时也鼓励人们用它来构建自己的项目。对于那些不熟悉机器学习的人,TensorFlow.js 是一个开放源代码库,让您可以直接在使用 Javascript 的浏览器中定义、训练和运行机器学习模型。特别值得一提的是,Pacman 网络摄像头控制器和 Teachable Machine 这两个演示似乎是很有趣的起点。
虽然这两个演示都是从网络摄像头选取输入图像,并输出基于训练数据的预测,但其内部运作却各不相同:
1) Pacman 网络摄像头 — 使用卷积神经网络选取输入图像(来自网络摄像头)并通过一系列卷积和最大池化层来传递图像。通过这种方式,它能提取图像的主要特征,并根据其训练时用过的示例预测其标签。由于训练过程费时费力,它使用了名为 MobileNet 的预训练模型进行迁移学习。该模型经受过基于 ImageNet 数据集的训练,可以区分 1000 种图像,而且经过优化,可以在浏览器和移动应用中运行。
2) Teachable Machine — 使用 kNN(最邻近规则分类),该分类非常简单,以至于从技术上说其完全不执行任何“学习”。它会接收输入图像(来自网络摄像头),并使用相似度函数或距离度量来寻找最接近该输入图像的训练示例的标签,从而对图像进行分类。然而,在馈送 kNN 之前,首先要通过名为 SqueezeNet 的小型神经网络传送该图像。然后,将该网络倒数第二个层的输出馈送到 kNN 中,该 kNN 允许您训练自己的分类。相较将来自网络摄像头的原始像素值直接馈送到 kNN 中,这样做的好处在于我们可以使用 SqueezeNet 已经学过的高层抽象,由此训练更好的分类器。
现在您可能想知道,手势的时效性怎么办?这两个系统都是每帧选取一个输入图像,并且在预测时完全不考虑面前的这些帧。这不是考查 RNN 时关注的整个要点吗?这对真正理解手势并非必要?利用在线资源学习本项目的 ASL 时,我发现,当做出手势时,不同手势之间手的始末姿势以及位置差别很大。与人交流时,其间发生的一切或许都很必要,但对机器而言,只使用始末姿势就足够了。因此,我决定弃常规作法而不用,无视过程,直奔目的。
事实证明,使用 TensorFlow.js 非常有用,其间原因包括:
我可以利用这些演示进行原型设计,而无需编写任何代码。最初开始原型设计时,我只是在浏览器中运行这些原始示例,用我计划使用的手势进行训练,然后观察系统的运行方式,即便其输出结果意味着是 Pacman 在屏幕上移动。
我可以使用 TensorFlow.js 在浏览器中直接运行模型。从可移植性、开发速度和与网页界面轻松交互的能力来看,这是巨大的。这些模型也可以完全在浏览器中运行,而无需向服务器发送数据。
因为模型在浏览器中运行,我可以将其与现代浏览器支持且我需要使用的语音转文字和文字转语音 API 连接起来。
它可以加速测试、训练和微调操作,而这往往是机器学习中的一大难题。
由于我没有手语数据集,并且训练示例基本是我在重复做出手语动作,因此使用网络摄像头来收集训练数据非常方便。
在全面测试这两种方法后,我发现这两个系统在测试中表现相当,于是决定使用 Teachable Machine 作为训练基础,这是因为:
如果使用较小的数据集,kNN 实际要比 CNN 运行地更快/更好。在用很多示例训练时,它们需要存储大量数据,性能也会下降,但我知道我的数据集不大,所以这不是问题。
由于 kNN 实际并非从示例中学习,所以它们不擅长泛化。因此,使用完全由一个人构成的示例训练模型,其预测结果并不能很好地迁移至另一个人。但对我而言,这也不是问题,因为我会通过自己反复做出手势来训练并测试模型。
该团队曾经开放过一个很好的项目剥离样板文件的源代码,可以作为一个非常有用的起点。
工作原理
下面我们从较高层面了解一下系统由始至终的运作方式:
在浏览器中打开网站后,第一步是提供训练示例。也就是,使用网络摄像头反复捕捉您自己做出每个手势时的动作。这样操作相对较快,因为只要按下特定的拍照按钮就可以持续捕获帧,直到您松开按钮并使用合适的标签标记捕获的图像。我训练的这个系统包含 14 个词汇,我可以将这些词汇进行多种组合,用来向 Alexa 发出不同请求。
训练完成后,输入预测模式。现在,系统会使用来自网络摄像头的输入图像并通过分类器运行,根据上一步中提供的训练示例和标签找到与其最邻近的类别。
如果超过了某个预测阈值,它会在屏幕的左侧附上标签。
然后我使用语音合成的 Web Speech API 来读出检测到的标签。
如果说出的单词是“Alexa”,则会唤醒附近的 Echo,并且 Echo 会开始聆听查询。另外值得注意的是,我创建了一个任意手势(在空中举起右拳)来表示单词 Alexa,因为 ASL 中不存在与这个词对应的手势,并且反复拼写 A-L-E-X-A 也会让人厌烦。
在完成整个手势短语后,我再次使用 Web Speech API 来转录 Echo 的反应,Echo 在回应这个查询时完全不知道该查询来自另一台机器。转录的反应显示在屏幕的右侧,以供用户阅读。
再次用手势表示唤醒词,清除屏幕并重启反复查询的流程。我已将全部代码上传至 Github,包括实时演示。您可以按照您的想法随意使用并修改代码。
虽然系统运行较好,但的确需要一些简单改动来帮助获取所需结果并提高准确度,例如:
确保在说出唤醒词 Alexa 之前不会检测到任何手势。
在训练示例中添加一个完整的泛类,我将其归为适用于空闲状态的“其他”类(空白背景、我站在一旁不做任何动作,手放在身体两边等)。这可以防止错误检测词汇。
在接收输出前设定高阈值,以减少预测错误。
降低预测率。不要按最大帧率进行预测,控制每秒的预测数量有助于减少预测错误。
确保预测时不会重复考虑短语中已经检测过的词汇。
在手语中,冠词通常不用手势表达,而是依靠上下文来表达意思,因此,我在训练模型时用了某些包含相应冠词或介词的词汇,例如这天气、这清单等。
另一个难题是,准确预测用户已在何时完成手势查询。这对于准确转录非常必要。如果太早触发转录(在用户完成手势之前),则系统会开始转录其自己的语音。但如果触发太晚,又可能会错过转录 Alexa 的部分反应。为了解决这一难题,我采用了两项独立的方法,两者各有利弊:
第一个选择是在添加词汇用于训练时,将这些词汇标记为终点词汇。我说的终点词汇是指用户只会在短语结束时使用的手势。例如,如果查询语句是 “Alexa, what’s the weather?”,则将 “the weather” 作为终点词汇,当检测到该词汇时,便可以正确触发转录。这个方法虽然很好用,但这意味着用户在训练期间必须要记得去标记终点词汇,并且需要依赖该词仅会出现在查询结尾的假设。这样,如果将查询语句调整为 “Alexa, what’s the weather in New York?”,则会面临问题。我们在演示中就使用了这个方法。
第二个选择是让用户故意做出一个停用词,让系统知道他们的查询语句结束了。识别到这个停用词后,系统就可以触发转录。所以用户需要用手势比出 唤醒词 > 查询 > 停用词。这个方法的风险在于,如果用户忘记用手势比出这个停用词,就会导致完全不会触发转录。我在另一个 Github 分支中运用了这个方法,您可以使用唤醒词 Alexa 作为查询 “书夹”,即 “Alexa, what’s the weather in New York” (Alexa)?”。
当然,如果有一种方法可以准确区分来自内部信源(笔记本电脑)和外部信源(附近的 Echo)的语音,就可以解决整个问题,但那又是另一个难题。
日后,我想会有许多其他方法可以解决这个问题,这或许可以作为您自己项目的良好起点,用于创建更稳健而通用的模型:
Tensorflow.js 还发布了 PoseNet,使用 PoseNet 也可能是一个有趣的方法。从机器的角度来看,只要追踪帧中手腕、手肘和肩膀的位置应该就足以对大多数词汇作出预测。在拼写词汇时,手指位置往往非常重要。
使用基于 CNN 的方法(例如 Pacman 示例)或许可以提高准确度,并使模型更能抵御平移不变性。该方法也有助于在不同人群中更好地实现泛化。用户也可以纳入保存模型或加载预训练 Keras 模型的能力,这些都有清晰的文件记录。这样,您就不必在每次重启浏览器时都要训练系统。
某些考虑时间特性的 CNN+RNN 或 PoseNet+RNN 组合可能会使准确性受到影响。
使用 tensorflow.js 中较新的可重用 kNN 分类器。
自我首次发布此项目以来,人们已在社交媒体上广泛分享,媒体也进行了相关报道,甚至 Amazon 也在 Echo Show 上实现上无障碍功能(点击唤醒 Alexa),用于帮助讲话困难的人士。虽然我并无证据可以证明是我的项目令他们得以实现这项功能(时间高度巧合),但如果确实如此,那真的非常酷。我希望 Amazon Show 或其他基于摄像头和屏幕的语音助手将来都可以纳入此项功能。对我而言,这可能是此原型展示的最终用例,并且让数百万新用户都能用上这些设备。
降低网络复杂度,并且采用简单地的架构来创建我的原型,这绝对有助于这个项目的快速推广。我的目标并不是要解决整个手语转文本问题。恰恰相反,我想要发起围绕包容性设计的对话,以平易亲和的方式介绍机器学习,并启发人们去探索这方面的问题,这才是我希望这个项目能达成的目标。
全部0条评论
快来发表一下你的评论吧 !