码农の带娃绝技:TensorFlow+传感器,200美元自制猜拳手套

简介:
本文来自AI新媒体量子位(QbitAI)

你们程序员啊,连带娃都这么技术流……

今年夏天,谷歌云负责维护开发者关系的Kaz Sato带着他的儿子,用一些传感器和一个简单的机器学习线性模型,开发了一个“猜拳机器”,能检测石头剪刀布的手势。

最近他还还根据这个过程写了一份教程,详细介绍了怎样构建这个机器,以及怎样用机器学习算法解决日常问题。

量子位搬运编译整理如下,适合有一定编程基础的同学,需要大约200美元的硬件设备。

我们先来看一下这个机器:


上面视频中,我们搭建的系统正在通过手套上的传感器,借助一个用Tensorflow编写的简单机器学习算法来检测我儿子的手势,然后选择相应的选项:石头、剪刀、布。

项目源代码在此:
https://github.com/kazunori279/ml-misc/tree/master/glove-sensor

具体是怎样实现的呢?接下来我会一步一步地讲。

第1步:
制作手套传感器

我们使用littleBits来构建硬件系统。这套设备对儿童很友好,包含各种各样的组件,如LED灯、电机、开关、传感器和控制器等,这些组件可以靠磁性链接,无需焊接。在这个实验中,我们使用了三个弯曲传感器,将它们附在塑料手套上。

fb4fa11a2fd078a16c4ef3d61a803d9e5e2ba95a

 littleBits弯曲传感器

当你戴着手套、弯曲手指时,传感器会输出一个从0V到5V变化的电压信号。为传感器输出加一个指示器,比如LED光柱,就能实时看到每个传感器受到的压力。


 弯曲传感器输出0V-5V信号

第2步:
安装Arduino和伺服模块

要读取弯曲传感器的输出信号并控制机器的转动幅度,我们使用了Arduino模块和伺服模块。Arduino模块内部有一个微控制器芯片,且具有多个输入和输出端口。你可以在笔记本电脑上用Processing语言(和C语言比较像)编写一个程序并编译,然后通过USB线传输到该模块中。

0e9a64d53079ac15b48b7038712ad842608aa103

 littleBits Arduino模块

1de6a3c7a5bd61590477924c3d00e7cd85f02a86

 伺服模块

e762fb9b9892d2db54c14041b2d1f09874dd565b

 我儿子在画转盘指示图

现在,用于构建猜拳机的所有硬件已经准确齐全,接下来,就该写代码了。

89d54cb90d912ce0da1ad30ccc089cb9cc315118

 猜拳机硬件部分

第3步:
写程序从弯曲传感器读取数据

在配置好硬件后,我们开始在Arduino模块上编写代码,实现从弯曲传感器读取数据的功能。在Arduino的IDE中,设定为每隔0.1秒读取传感器数据,然后将其记录在串行控制台上,代码如下。

a439b2904a78287fe4c75c06bb0cb65a09a82036

 在Arduino IDE中编写程序

运行这段代码时,你会在控制台上看到这样的数字:

848c7477cdf87c03c3d794c98b72719d21e7e6b7


其中,每行的三个数字表示弯曲传感器输出的三个数据。Arduino模块将输入信号电压(0V - 5V)转换成从0到1023变化的数字。

上图是“石头”手势的数据,所有传感器都是弯曲的。如果换成“布”的收拾,所有传感器都不弯曲,则上图的数据都会趋近于0。

第4步:
使用Cloud Datalab可视化数据

该如何确定这三个数字的组合是代表着“石头”、“布”还是“剪刀”?

最简单的方法是编写能判断阈值和条件的IF语句。比如:

  • 当三个输出数值都低于100时,则输出“布”;
  • 当三个输出数值都高于400时,则输出“石头”;
  • 若不满足以上两个条件,则输出“剪刀”。

这个程序可能满足当前任务的要求,但是很不灵活也不稳定。

如果我儿子要求我在手套上添加更多传感器,来捕获10个不同手势,那该怎么办?或者,如何向紧身衣添加多个传感器,来识别不同身体姿势?显然,上述程序无法处理这么复杂的任务。

当然,主要是因为我比较懒,想编写出更强大和更灵活的代码,能在不改变基本设计的前提下,灵活处理善变的甲方(我儿子)可能提出的各种请求。

为了找到更好的数据处理方法,我对手套传感器数据做了一些快速的分析。我使用的工具是Cloud Datalab,这是一个很受欢迎的Jupyter Notebook版本,并已集成到Google Cloud平台,可提供基于云数据分析的一站式服务。你可以在Web UI中编写Python代码,使用如NumPy、Scikit-learning和TensorFlow等函数库,并将其与Google Cloud服务(如BigQuery、Cloud Dataflow和Cloud ML Engine)相结合。

根据不同手势,我把手套传感器数据分开保存成三个CSV文件,每个文件包含800行数据。你可以在Cloud Datalab上编写Python代码,将它们读取并转换为NumPy数组,示例代码如下:

8a72756da84e5c1f6dfa5809c2d4dcb4a6c70329

 使用Cloud Datalab读取CSV文件转为NumPy数组

完整代码:https://github.com/kazunori279/ml-misc/blob/master/glove-sensor/Rock-paper-scissors.ipynb

你也可以使用Matplotlib库来可视化NumPy数组。下面代码画出了一个3D图,其中每个轴对应着一个不同传感器。

00dcb30371e18870de8eca4669fda13f3dc20b3f

a11201755bbcbd89e9eb1f80388fc61b9c9f46de

 用3D图绘制传感器数据,已缩放原始多维数据

通过观察上面的3D图,你可以更清楚地看到数据的空间分布。

第5步:
创建一个线性模型

接下来,我们要将这些原始传感器数据划分到三种不同手势类别中。这里会用到线性代数,你们在高中或者大学应该都学过了。

线性代数是一种可以将某个空间映射到另一空间的数学方法。例如,下面公式表示了一种从某个一维空间到另一个一维空间的线性映射。

817a540ba00c0c8f95a61a55d6fa9ec16c1d7c8d

 一元公式

其中,x和y分别为两个一维空间中的变量,w为权重,b为偏差。利用这个公式,可以将一维空间“纽约市出租车的行驶距离”映射到另一个一维空间“出租车费用”,其中把2.5美元(每英里费用)作为权重,把3.3美元(初始费用)设置为偏差。

d2074796f579a40730880e2a38c125f1a62ac579

 “行驶距离”和“出租车费用”的映射函数

从图中看出,权重和偏差(也叫参数)分别定义该直线的斜率和初始位置。你可以通过调整这些参数,来创建从一个一维空间到另一个一维空间的任何线性映射。

线性代数的优点在于,在从任意m维空间到任意n维空间进行线性映射时,可使用相同公式。例如,在将三维空间(x1,x2,x3)中的某个点映射到另一个三维空间(y1,y2,y3)中,均可使用以下公式。

352171315aae0b08cae1fec9cbb821eba61ea0c8

 三维空间之间的映射函数

数学家认为上面公式写得太冗长了,所以设计了一种更容易的表示方法:矩阵乘法。

三维映射关系也可以这么表示:

51147d9d0abe1b7204463998bd042ba774aa6be9

或者,更简单写成:

d1cf3d5d4e843016cf1390278ceb26f064cf1e0c

其中,x和y为3维列向量,W为3×3的权重矩阵,b是3维偏置列向量。是的,它与一维空间的映射函数完全一样。而且,该公式可应用于m维空间和n维空间之间的任何线性映射,这就是所谓的“线性模型”。

那么,线性模型在本项目能起到什么作用呢?我们可以利用它,将“手套传感器数据”的3维空间转换为“石头剪刀布”的3维空间,如下所示:

d1519776da49944a5e52a274a3c34c16091037ea

 3维空间的动态转换

在完成手套传感器数据与“石头剪刀布”3维空间的配对后,很容易写出用于分类的IF语句,如下:

  • 当石头方向值高于其他方向,则输出“石头”;
  • 当布方向值高于其他方向,则输出“布”;
  • 当剪刀方向值高于其他方向,则输出“剪刀”。

线性模型可以将原始输入数据转换到特征空间中,在该空间中可为要捕获的每个特征设定不同方向,这样更容易处理转换后的数据。这就是为什么我认为线性代数不仅是数据科学家的奇妙数学工具,对懒惰的程序员来说也是如此。

在输入数据有多个维度或是有多个不同属性时,线性模型尤为重要。

比如,当你将几十个弯曲传感器连接到紧身衣后,则可使用线性模型将来自传感器的原始数据映射到用多个方向来表示不同身体姿势的特征空间(如站立、坐着或蹲下等),无须基于原始数据来编写很多不稳定的IF语句。

当然,线性模型还可处理非结构化或稠密数据,以提取所需的特定特征。这类数据的维度一般为数百个,甚至数千个,如图像、音频、自然语言和时间序列数据等。

但请注意,线性模型并不是万能灵药。

要在复杂的非结构化或稠密数据的分类任务中达到更高的正确率,可能要使用非线性模型,如神经网络或支持向量机。这样,你可以通过非线性变换来提取有用的特征,这种非线性变换能以一种更灵活的方式来调整原始数据。

在刚开始处理复杂数据时,你可以先尝试使用线性模型,如果不能提取满足要求的所需特征,可进一步尝试非线性模型来获得更好效果。

第6步:
让TensorFlow寻找参数

既然我们已经了解线性模型十分有用和强大,你可能想知道:

该如何确定最佳映射的参数(即权重和偏差)?

答案是:机器学习。

你可以利用机器学习,让计算机根据测得的输入数据来计算线性模型的最佳参数组合。利用TensorFlow能很容易地实现这些想法。在TensorFlow中,只需将线性模型的公式“y = Wx + b”公式定义为计算图,如下:

285defa9e0532eadd98469c605c42c0648ae5b25

在上面代码中,tf.Variable创建两个初始化为0的变量,分别保存了3 x 3的权重矩阵和3维偏置列向量。此外,tf.placeholder创建一个占位符,可接收任何数目的手套传感器数据作为输入;tf.matmul是手套传感器数据和权重进行矩阵乘法的函数,并根据tf.matmul用法,将手套数据置于前者。

请注意,当你调用这些函数(TensorFlow中的低级API)时,不执行任何计算,只是建立一个计算图,如下所示:

3a5afed07370ee46cd6fba1f86a9b4b1a20e74dc

 计算图

机器学习和TensorFlow的强大在于,可利用计算机寻找最佳参数(包括权重和偏差)。在上面例子中,我们输入了手套的三个传感器数据及其期望输出(有石头、剪刀或布)。TensorFlow可利用该数据,在图中进行反向计算,寻找最佳的权重和偏差以得到期望的线性变换。这个过程叫做“训练机器学习模型”。

利用机器学习,你只需设定输入和输出,即可利用计算机训练得到最佳的映射函数,这就跟自动编程一样。在21世纪,机器学习从某种程度上可看作是一种工程师的计算器。任何人都可以使用它来执行简单任务,以减少编码工作。

第7步:
定义一个训练“教练”

训练线性模型时,需要一个监督“教练”。我们通过以下两行代码来引导模型训练,以达到期望效果。

27410741b9db58a026873ccf88fb7eb01207ecc9

rps_labels是用来接收每行手套传感器数据标签的占位符,为每个手套传感器数据按照一定格式来定义标签,如下所示:

46e5b98c49e90d7b04bdba8f33248e9f16b6eed4

其中,[1 0 0]表示石头,[0 1 0]为布,[0 0 1]为剪刀,这叫做one-hot编码,是在训练分类模型中表示标签的一种通俗方法。

在第二行,我们调用了tf.losses.softmax_cross_entropy来定义损失函数。关于softmax、交叉熵以及损失函数的详细介绍,可参考维基百科。对于这三者,你只要了解以下内容:

  • Softmax能将rps_data中的数值对应压缩到区间[0, 1],这样可将其输出作为石头、布和剪刀的估计概率。
  • 交叉熵返回两个概率分布间的差异程度:rps_labels中的one-hot标签(真实值)和softmax函数输出的估计概率。
  • 损失函数是一个衡量模型实际准确程度的函数。因此,我们使用交叉熵作为损失函数。

7832d207fe3f0818bf170bdf1ee4593ee18c21de

 “交叉熵指出了实际标签与计算概率间的差异度”

——Martin Gorner, TensorFlow and deep learning, without a PhD

在这种情况下,损失函数看作是softmax函数和交叉熵的组合体,指出当前参数在线性模型中对应的误差值。该函数是TensorFlow中的“教练”,引导模型沿着正确方向寻找最佳参数。

顺便提一下,线性模型和softmax函数的组合被叫做多元逻辑回归(multinomial logistic regression),或是softmax回归,这是在统计学和机器学习中一种常用的分类算法。

第8步:
训练线性模型

接下来,我们准备在TensorFlow中加入优化器来训练模型。

45170acbd89c252b517836d266750d3d0dd4c05d

tf.train.GradientDescentOptimizer是TensorFlow中一种常用的优化器,通过梯度下降算法调整参数,来最小化损失函数返回的误差。

在实际训练中,你需要创建一个会话(Session)并将手套传感器数据和标签传给优化器。由于优化器会以指定的学习速率逐渐地更改参数值,因此可能要运行多达数千次。观察训练过程中的损失值,你可以发现它在逐渐减小,这意味着模型的错误率越来越低。

e3a504c24e55dc3cc66687095d5e216ff9fd9134

在训练结束后,你将获取一系列训练好的权重和偏差,可利用softmax概率将手套传感器数据映射到相应决策空间。在原始手套传感器空间中绘制softmax概率分布,如下所示。

4bfcf0768592ee2b10d3d689df6d5486842a8dfc

 石头、布和剪刀的估计概率分布

第9步:
在Arduino上运用线性模型

我们已经得到了一种能分类手套传感器数据的实用方法,接下来完成对Arduino的编码。

在Datalab上运行sess.run(weights),可输出训练好的权重值。复制这些权重值并写入Arduino代码中,对偏置也进行以下操作。

fdc23b510025ce3a575e3822826cc141ca2854dc

最后,利用Arduino中的线性模型,可将手套传感器数据映射到决策空间。你可以用下面的Arduino代码来实现数据、权重和偏差间的矩阵乘法计算。

d2e396a0d4fb9eba0de20bdc4e740ac40c0574de

然后,比较这些值并找到最大值。一旦确定了手套表示的手势,Servo就可以正确控制机器手并赢得比赛。在这个例子中,你不需要计算出softmax值,只需比较下线性变换的三个输出值,其中这三个值分别对应着石头、布和剪刀。

ea91658b1f47f7ed569ad2a041fa80ee06a2a7bf

到这里已经完成了,你可以使用机器学习来创建专属于你的石头布剪刀机器。

下一步计划

正如这篇文章中提到的,线性模型是一个强大的工具,可以通过线性变换将任意的m维空间映射到n维空间。如果你觉得编写多个if语句来校验复杂条件下的原始输入数据过于乏味,可以考虑这种工具。与直接处理原始数据不同,在处理能映射到特征空间中的数据时,这种方法更为简单。在这篇文章中,特征空间指的是石头、布和剪刀的决策空间。

这里用到的关键技术是机器学习和TensorFlow,在构建线性模型可帮助你找到最佳参数。这些技术不仅只是针对深度学习和人工智能的工具,也可用于为各种编程任务构建出一个强大且灵活的代码。

原文地址:https://cloud.google.com/blog/big-data/2017/10/my-summer-project-a-rock-paper-scissors-machine-built-on-tensorflow

本文作者:王小新 
原文发布时间:2017-10-16
相关文章
|
7月前
|
容器
星际争霸之小霸王之小蜜蜂(九)--狂鼠之灾
星际争霸之小霸王之小蜜蜂(九)--狂鼠之灾
|
6月前
|
传感器 前端开发 iOS开发
实战编程·刻在男人DNA里的浪漫,空气投篮(二)(4)
实战编程·刻在男人DNA里的浪漫,空气投篮(二)
32 1
|
6月前
|
存储 Go iOS开发
实战编程·刻在男人DNA里的浪漫,空气投篮(二)(2)
实战编程·刻在男人DNA里的浪漫,空气投篮(二)
30 1
|
6月前
|
存储
实战编程·刻在男人DNA里的浪漫,空气投篮(二)(1)
实战编程·刻在男人DNA里的浪漫,空气投篮(二)
34 1
|
6月前
|
容器
实战编程·刻在男人DNA里的浪漫,空气投篮(二)(3)
实战编程·刻在男人DNA里的浪漫,空气投篮(二)
30 0
|
6月前
|
安全 iOS开发 UED
实战编程·刻在男人DNA里的浪漫,空气投篮(一)
实战编程·刻在男人DNA里的浪漫,空气投篮(一)
51 0
|
7月前
|
存储
星际争霸之小霸王之小蜜蜂(八)--蓝皮鼠和大脸猫
星际争霸之小霸王之小蜜蜂(八)--蓝皮鼠和大脸猫
星际争霸之小霸王之小蜜蜂(五)--为小蜜蜂降速
星际争霸之小霸王之小蜜蜂(五)--为小蜜蜂降速
星际争霸之小霸王之小蜜蜂(十)--鼠道
星际争霸之小霸王之小蜜蜂(十)--鼠道