×
嵌入式 > 技术百科 > 详情

时代的变革需要创新者——具备神经网络记忆,实现深度学习的开源Arduino 101体验

发布时间:2020-06-17 发布时间:
|

提到Arduino,相信大部分工程师不会陌生。目前所说的Arduino,不应该简单的理解为单一的硬件开发板,还应包括广泛生态圈支持、开源社区、完整的项目应用等,这是一个覆盖面广,生态圈体系完整的开源项目工程,简单,易用,让每一个喜欢DIY的电子爱好者享受创新的乐趣。当然,随着硬件的不断更新迭代,原本的Arduino硬件平台在性能上还是有所制约的,这可能是Arduino爱好者的一大遗憾。

Arduino 101开发板

不过,就在去年,Arduino社区联合Intel发布了基于Intel Curie模组的Arduino 101/Genuino 101开源硬件板,虽然叫了两个不同的名字,其实这两个版本在硬件上是一致的,不同的名字以及不同的颜色只为区分发售地区,比如,Arduino 101只在USA发售,而Genuino 101则是在除了USA以外的地区发售的正版货。相比曾经的Arduino开发板,Arduino 101/Genuino 101在性能和功能上都有了一定的提升,但是最让笔者兴奋的是开发板上搭载的Intel Curie模组中具备神经网络记忆单元,也就是说,我们可以拿Arduino 101/Genuino 101开发板做深度学习方面的开发,这就比较牛逼了,毕竟目前真正实现量产商用的神经记忆芯片没几家,而Intel Curie恰好是其中之一。比较幸运的是,前段时间笔者恰好拿到了Arduino 101,了解了一番,迫不及待拿出来与大家分享。

由于是开源的产品,资料啥的在网上都能找到,所以包装盒内部除了板卡就只有一张感谢卡。

说真的,以前玩过的Arduino开发板都是某宝上花几十块钱买来的,功能和正版虽然没啥区别,但是能明显看出做工与正版的差别,尤其是PCB板材。而此刻手上拿到的Arduino 101给我最大的感受就是做工精良,除了一些黑心商家,一般来说,质量跟价格确实是成正比的,所以如果有啥重要的项目啥的,千万别贪图小便宜。

有点尴尬的是正版的Arduino 101采用的是USB B型接口,并不是我们常见的MicroUSB或者miniUSB数据线,所以这数据/电源线要另外购买。另外,可以看到在Arduino 101开发板USB接口两侧有两个复位按键,“MASTER RESET”以及“RESET”按键,MASTER RESET是用来复位整个Intel Curie模组的,而RESET则是用来复位其上运行的Arduino程序。接触过Intel Galileo开发板的朋友对这一定非常熟悉,同样也是提供一些API供用户在Arduino开发方式下调用。

依托于板载Intel Curie模组的超强集成度,板子本身布局非常简洁,板载的器件也一目了然。其中TI的电平转换IC主要用于intel Curie模组和Arduino标准IO的电平转换。

Arduino开发板主要的板载资源:

Intel Curie模组

Arduino 101板子上的核心芯片为Intel Curie模组,整个模组只有11mm*8.05mm大小,高1.95mm,非常小巧,但是集成度可不低,内部集成了一个Intel x86 Quark SE微控制器以及一个32bit的ARC架构的核心(两个核心共用Flash和RAM)、Nodic NRF51822低功耗蓝牙芯片、bosch 6轴加速度/陀螺仪传感器、多个LDO以及DC/DC转换器、内部电池充电单元等,功能可谓十分强大,非常适合既需要性能又有空间限制的穿戴设备和物联网产品开发。

Intel Curie模组硬件系统框图如下。

其实这部分内容在爱板网的Curie Nano评测中也都有提及过,但是没有进一步细化,本文既然讲到了Arduino 101的神经网络记忆功能,那必须要更深入的了解这颗Intel Curie模组,上面已经提及这颗模组内部集成了一个Intel Quark SE的微控制器,而神经网络记忆功能就是在这颗Quark SE微控制器中。

  • 神经元Neurons

Quark SE中集成了128个neurons(神经元),我们文章一开始所说的神经网络记忆功能的核心就是在这些neurons上。神经元的工作原理是什么?有什么优势?

首先,必须要清楚neurons也不是神,它也是需要学习认知的,所以,它也有个学习的过程,如下图所示。

比如我们平时所见到的图像、文本、视频,听到的声音等,这些都是没法直接被neurons认知的,这些源数据都需要通过传感器采集,然后从中提取出相应的特征向量,然后neurons去学习、认知,而在Quark SE微控制器有一个限制是内部只有128个神经元,这意味着这些特征向量是有限制的,最大长度不能超过128bytes。这不,有人就有疑问了,如果去采集一张超过128pixels的图片该怎么处理,没办法了吗?这时我们就不用那么死板,可以做二次甚至是三次采用来生成一个不大于128bytes的特征向量。

下图则是neurons从学习到认知的工作流程图

虽然neurons挺起来很玄乎,但可以这么简单的理解,你可以把他当做一个刚出生的小孩,什么都不会,然后你开始慢慢教他东西,慢慢地他学会了你教的东西,也能举一反三,比如在经过一段时间学习后,你拿出新的胡萝卜给它识别,它就能从它学习到的知识库中快速的认出,这就是胡萝卜。

但机器终究是机器,还是局限在了一个大的框架中,而人类则有无限的可能,所以综合来讲,neurons确实可以做深度学习,但仅限于简单的深度学习,至少在硬件层面上决定了它不可能很复杂,算是初级的人工智能的一个小尝试吧。

通过对neurons的了解,我们可以简单的总结出几条neurons的优势,比如

  • neurons通过已经已经编好的程序进行学习,不需要进一步编程,可以离线自主学习
  • 只需极低的功耗就能持续学习
  • 可以侦测新事物或者异常的事物并报告状态
  • neurons内部的学习认知数据可以复制或移植到其他地方

当然,下面我还将用实际的事例进一步说明neurons如何实现深度学习的。

Neurons功能实际应用

Arduino 101的开发环境就不必多做介绍了,用Arduino IDE工具就行,如果没有Arduino 101的支持包,需要先在开发板管理器中安装,截止到目前,最新的版本应该是2.0.2。

当然,由于这会要体验neurons的功能,还需要相应的库支持,neurons的库CurieNeurons是由General Vision这家公司提供,分为免费版和专业版,专业版还是要收费的,售价19美金,说好的开源呢?看来开源也仅限于免费版。

从介绍上来看,专业版除了包含免费版的全部功能,还包括了完整的读取神经元寄存器的源代码,尤其是几个特别强大的算法,如通过神经网络分割上下文能力的算法、是选择L1或者LSup规范作为距离测算的算法、使用径向基函数或者最邻近规则分类算法的代码等(当然,有些事针对CM1K芯片的,这颗神经网络芯片是chipX公司开发的),如果要深入,每一个都是一个大课题或者大研究项目,能售价19美金,还是物有所值的。不过对于笔者来说,目前来说免费版已经能凑合着用了。

通过Arduino IDE载入下载完成的CurieNeurons库,我们就可以直接使用几个免费的应用。

这里选择IMU 6轴加速度计/陀螺仪的应用,采集相关数据,对Arduino 101当前运动状态和姿态的学习和认知。源代码如下

#include "CurieIMU.h"

int ax, ay, az; // accelerometer values
int gx, gy, gz; // gyrometer values

int calibrateOffsets = 1; // int to determine whether calibration takes place or not

#include
CurieNeurons hNN;

int catL=0; // category to learn
int prevcat=0; // previously recognized category
int dist, cat, nid, nsr, ncount; // response from the neurons

//
// Variables used for the calculation of the feature vector
//
#define sampleNbr 10 // number of samples to assemble a vector
#define signalNbr 6 // ax,ay,az,gx,gy,gz
int raw_vector[sampleNbr*signalNbr]; // vector accumulating the raw sensor data
byte vector[sampleNbr*signalNbr]; // vector holding the pattern to learn or recognize
int mina=0xFFFF, maxa=0, ming=0xFFFF, maxg=0, da=0, dg=0;

void setup()
{
Serial.begin(9600); // initialize Serial communication
while (!Serial); // wait for the serial port to open

// initialize device
Serial.println("Initializing IMU device...");
CurieIMU.begin();

// use the code below to calibrate accel/gyro offset values
if (calibrateOffsets == 1)
{
Serial.println("About to calibrate. Make sure your board is stable and upright");
delay(5000);
Serial.print("Starting Gyroscope calibration and enabling offset compensation...");
CurieIMU.autoCalibrateGyroOffset();
Serial.println(" Done");
Serial.print("Starting Acceleration calibration and enabling offset compensation...");
CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1);
Serial.println(" Done");

CurieIMU.setAccelerometerRange(8);
CurieIMU.setGyroRange(1000);
}
// Initialize the neurons and set a conservative Max Influence Field
if (hNN.begin()==0) Serial.print("\nNeural network is initialized!");
else Serial.print("\nNeural network is NOT properly connected!");
hNN.forget(500); //set a conservative Max Influence Field prior to learning

Serial.print("\n\nEntering loop...");
Serial.print("\nMove the module vertically or horizontally...");
Serial.print("\ntype 1 + Enter if vertical motion");
Serial.print("\ntype 2 + Enter if horizontal motion");
Serial.print("\ntype 0 + Enter for any other motion");
}

void loop()
{
// Learn if push button depressed and report if a new neuron is committed
//
// wait for a keyboard input of 1 digit between 0-2
// WARNING: make sure the serial printer settings is "new line"
//
if (Serial.available() == 2)
{
catL = Serial.read();
Serial.read(); // to empty serial buffer of the newline char
catL = catL - 48;
if (catL<3) //expected category input (1-vertical, 2-horizontal, 0-still)
{
Serial.print("\nLearning motion category "); Serial.print(catL);
//
// learn 5 consecutive sample vectors
// (make sure to keep moving the 101 board accordingly)
//
for (int i=0; i<5; i++)
{
extractFeatureVector(); // the vector array is a global
//Optional display of the vector in the serial monitor
//Serial.print("\nVector = ");
//for (int i=0; i ncount=hNN.learn(vector, sampleNbr*signalNbr, catL);
}
Serial.print("\tNeurons="); Serial.print(ncount);
}
}
else
{
// Recognize
extractFeatureVector(); // the vector array is a global
hNN.classify(vector, sampleNbr*signalNbr,&dist, &cat, &nid);
if (cat!=prevcat)
{
if (cat!=0x7FFF)
{
Serial.print("\nMotion category #"); Serial.print(cat);
}
else Serial.print("\nMotion unknown");
prevcat=cat;
}
}
}

// The following function is very academic and assemble a pattern which
// should be more sophisticated for real-life system taking a calibration into account,
// integrating a sampling rate adequate for the type of motion and profiling the waveforms
// more selectively (distances between peaks and zero crossing, etc)

void extractFeatureVector()
{
// sensor output [ax,ay,az,gx, gy,gz] is converted into a byte array as follows:
// [ax'1, ay'1, az'1, gx'1,gy'1, gz'1, ax'2, ay'2, az'2, gx'2, gy'2, gz'2, ...] over a number of time samples.
// a' and g' are normalized using their respective min and max values.
//
// the reset of the min and max values is optional depending if you want to
// use a running min and max from the launch of the script or not
mina=0xFFFF, maxa=0, ming=0xFFFF, maxg=0, da=0, dg=0;

for (int sampleId=0; sampleId {
//Build the vector over sampleNbr and broadcast to the neurons
CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz);

// update the running min/max for the a signals
if (ax>maxa) maxa=ax; else if (ax if (ay>maxa) maxa=ay; else if (ay if (az>maxa) maxa=az; else if (az da= maxa-mina;

// update the running min/max for the g signals
if (gx>maxg) maxg=gx; else if (gx if (gy>maxg) maxg=gy; else if (gy if (gz>maxg) maxg=gz; else if (gz dg= maxg-ming;

// accumulate the sensor data
raw_vector[sampleId*signalNbr]= ax;
raw_vector[(sampleId*signalNbr)+1]= ay;
raw_vector[(sampleId*signalNbr)+2]= az;
raw_vector[(sampleId*signalNbr)+3]= gx;
raw_vector[(sampleId*signalNbr)+4]= gy;
raw_vector[(sampleId*signalNbr)+5]= gz;
}

// normalize vector
for(int sampleId=0; sampleId < sampleNbr; sampleId++)
{
for(int i=0; i<3; i++)
{
vector[sampleId*signalNbr+i] = (((raw_vector[sampleId*signalNbr+i] - mina) * 255)/da) & 0x00FF;
vector[sampleId*signalNbr+3+i] = (((raw_vector[sampleId*signalNbr+3+i] - ming) * 255)/dg) & 0x00FF;
}
}
}

编译OK,下载OK。

打开Arduino IDE工具的串口监视器,首先我们可以看到程序会初始化以及校准IMU6轴加速度计/陀螺仪,然后Neurons初始化,到这里我们就可以开始教Arduino 101学习。

此例程建议我们学习的三种模式:

输入“1”并按下回车来学习认识开发板的垂直状态;

输入“2”并按下回车来学习认识开发板的水平状态;

输入“0”并按下回车来学习认识开发板的其他动作或状态;

比如你将板卡水平放置,输入1回车,此时neurons会记录下这个状态学习。

同样,将板卡锤子放置,输入2,此时neurons会记录下这个状态学习。

当然,你还可以实现更多复杂的状态操作,但是仅限于演示的例程我们只例举以上两种方便判断的状态,然后我们可以使板卡呈现水平或者垂直的状态,Arduino 101(neurons)能快速的反应出这个状态。当然,如果要更准确,那可能需要更周密的算法以及精度更高的传感器支持。

另外看下其它的几个有关神经网络记忆例程,比如数字模组的分类,看文档介绍这个是针对chipX公司的CM1K神经网络芯片的,但是实际代码确实时针对Arduino 101开发板的,所以我们也可以拿来使用,这个分类的原理就跟我们之前介绍的比较类似,简要流程如下图所示(其中可以很明显的看到这个是以CM1K神经网络芯片作为例子的)

接收源数据,提取源数据,将提取的数据广播到现有的神经元中,如果有相同分类的归在一起,不同分类的重新新建神经元,然后这组数据再组成一个新的类别。

源代码就不放出来了,有需要的直接可以去General Vision官网下载免费的。最后的学习认知过程如下图的Log所示。这里有几点需要说明下,因为是例程,所以尽量用最简单的方式说明问题,比如所例举的向量都是默认从源数据中已经提取了,并且都是相同的,精简的,如11,11,11,11;15,15,15,15这些,换句话说,这个就是打个比方。

从上面的Log上来看,第一段说明Arduino 101上的101个neurons都未被使用,第二段是开始学习,给了3个向量模型,也占用了3个neurons,而且给每个向量模型归类为55,33,100,其中有一个注意点,故意将第二个neurons的category设为比第一个neurons的catagory小,从第三段可以看出,这个例程神经元的判别依据是以catagory从小到大来的,所以在认知13,13,13,13这个向量数组来说先识别出catagory33然后才识别出catagory55;Log中最后一段要将13,13,13,13这个数组识别为新的模型。如果想要详细的了解学习认知模型的原理,点此处进入。

文章中关于Intel Curie模组的BLE功能等就不再介绍了,有需要了解的可以参考爱板网之前的评测Curie Nano,本文主要是体验下笔者非常感兴趣的神经元,体验深度学习。

总之,对于笔者来说,深度学习还是很玄乎的,因为你会发现,越去了解,你越会发现这个系统复杂(我都不能确定到底能不能称之为系统),越会发现自身的不足之处,但这不妨碍你去体验Arduino 101,如果对这方面感兴趣的朋友,不妨买一块来玩玩,毕竟新技术还是很有吸引力的,以后也可以很装逼的对别人说,我在从事“深度学习”这方面的工作。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
分拣机器人的工作原理是什么