Arduino前馈反向传播神经网络

本文介绍了为Arduino Uno微控制器板开发的人工神经网络。 这里描述的网络是前馈反向传播网络,可能是最常见的类型。 它被认为是有监督或无监督学习的良好通用网络。 该项目的代码以Arduino Sketch的形式提供。 它是即插即用的-您可以将其上传到Uno并运行它,并且有一部分配置信息可用于快速构建和训练自定义网络。 这里提供的文章概述了人工神经网络,Sketch的详细信息,并介绍了前馈网络和反向传播算法中使用的一些基本概念。

自1980年代中期以来,一直在使用反向传播神经网络。 反向传播的基本概念非常简单,尽管算法本身包含一些高阶数学,但不必完全了解如何导出方程式即可应用它们。 在非常小的系统上实现网络存在一些挑战,而在较早的廉价微控制器和业余爱好者主板上,这些挑战是巨大的。 但是,Arduinos和今天的许多开发板一样,实际上使这项工作非常短。 此处使用的Arduino Uno基于Atmel的ATmega328微控制器。 它的2K SRAM足以用于具有7个输入和4个输出的示例网络,并且由于Arduino的GCC语言支持多维数组和浮点数学运算,因此编程工作非常容易管理。

神经网络已用于从自动驾驶汽车控制到游戏,面部识别到股票市场分析的各种应用中。 大多数应用程序将涉及某种类型的模式匹配,在这种情况下,系统的确切输入将是未知的,并且可能缺少或多余的信息。 考虑识别手写字符的问题。 字母表的一般形状可以提前知道,但是实际输入将始终变化。 当然,在ATmega328上建立的小型网络并不能完全满足面部识别的任务,但是可以掌握很多的机器人控制和机器学习实验。

顾名思义,人工神经网络(通常缩写为ANN)是受自然启发的计算模型。 这是在某种程度上模仿大脑存储信息并对各种输入做出反应的方式的尝试。 在自然界中,神经系统的基本组成部分是一种特殊类型的细胞,称为神经元。

将神经元可视为一个微小的电化学开关可能会很方便,它在受刺激时会打开。 神经元在广阔的网络中相互连接。 当神经元被刺激激发并变得活跃时,它会沿着该网络发送少量电荷,进而使网络中的其他神经元变得活跃。 一个神经元将有多个神经元馈入其中,并且这些连接的强度也会变化。 如果输入有很强的联系,它将提供很多刺激。 较弱的连接将提供较少的连接。 在非常真实的意义上,神经元可以被认为是将所有这些具有不同强度的输入相加,并基于总和产生输出。

在基于软件的人工神经网络中,神经元及其连接被构建为数学关系。 当软件具有输入模式时,它将通过网络馈入该模式,系统地将输入添加到每个神经元,计算该神经元的输出,并使用该输出将适当的输入馈送到网络中的其他神经元。

确定神经元之间的连接强度(也称为权重)成为神经网络应用程序中的首要任务。 在反向传播算法中,最初使用随机权重初始化网络。 然后为网络提供输入和输出的训练集。 当输入通过系统馈送时,会将实际输出与所需输出进行比较,并计算误差。 然后,该错误会通过网络反馈,并且权重会根据学习算法进行增量调整。 在许多周期(通常是数千个周期)中,最终将对网络进行训练,并在出现输入时给出正确的输出。

在我们在此处构建的前馈网络中,神经元分为三层,分别称为输入层,隐藏层和输出层。 一层中的所有神经元都连接到下一层中的所有神经元。 下面是这种关系的经典图形表示。

隐藏层在前馈网络中起着至关重要的作用。 在早期的神经网络模型中,输入神经元直接连接到输出神经元,并且网络可以实现的解决方案范围极为有限。 两层模型无法解决的此类问题之一是“异或”或-通常表示为XOR的逻辑。 在布尔逻辑中,当两个输入中的任何一个为true时,XOR关系将为true,但是当两个输入均为true时,则为false。 XOR的真值表如下图所示。

通过在输入和输出之间增加一层,网络可以解决XOR等问题。 一些理论认为,在优化网络的其他条件的情况下,三层网络将能够解决任何真值表。 解决XOR是对新网络的良好测试。 您会在示例中看到它经常使用,并且它通常被称为神经网络的“ Hello World”程序。

本文随附的Sketch中实现的网络只是一个演示,实际上并没有执行任何实际功能。 Sketch包括一组训练输入和输出,并且将网络训练到该集合,直到达到预定的准确度为止。 此时,Sketch宣告胜利,然后重新开始。 在此过程中,培训结果会定期发送到使用Arduino IDE或任何其他终端程序的串行监视器进行监视的串行端口。 (请注意,使用Arduino IDE时,在加载Sketch后有必要在“工具”菜单中启动串行监视器。)

该程序的结构使得通过简单地在Sketch开始处更改配置部分中的值就可以非常快速地组装网络和训练集。 这种设置可以在不必了解所有潜在细微差别的情况下进行网络实验。 \

配置部分包括两个数据数组Input和Target,它们共同构成训练集的真值表。 就其价值而言,Sketch中设置的训练是一个真值表,该真值表将led数字显示(0-9)的七个段映射为二进制数(0000-1001)。 您可能会认为这是光学字符识别问题的基本表示。 如果研究数组,您会发现它们在输入和输出的映射中提供了丰富的组合,并为网络可以学习解决相当困难的问题提供了很好的概念证明。

要将网络修改为新的训练集,必须在输入和目标数组中输入适当的真值表值,并且还必须在配置部分中调整相应的参数以匹配新的真值表:

  • PatternCount:真值表中训练项目或行的数量
  • InputNodes:输入神经元的数量
  • OutputNodes:输出神经元的数量

您可以在本节中配置一些其他项目进行实验。

  • HiddenNodes:隐藏神经元的数量
  • LearningRate:调整实际反向传播该错误的数量
  • Momentum:调整前一次迭代的结果对当前迭代的影响程度
  • InitialWeightMax:设置重量的最大起始值
  • Success:网络解决了训练集的错误阈值

作为一个一般概念,HiddenNodes,LearningRate,Momentum和InitialWeightMax一起工作以优化网络以提高学习效率和速度,同时最大程度地减少神经网络设计中遇到的陷阱。

较低的LearningRate值会导致训练过程变慢,但会降低网络陷入振荡的可能性,在这种情况下,网络会持续过度解决训练问题的解决方案,并且永远不会达到成功阈值。 在我们的演示中,LearningRate设置为3.。 对于大型,非常复杂的网络(比我们在Arduino Uno上构建的网络大得多),该值通常设置得非常低-约为0.01。

Momentum通过将先前反向传播的一部分注入当前反向传播来平滑训练过程。 Momentum有助于防止网络收敛于一个好的但不是最好的解决方案的现象,也称为收敛于局部最小值。 Momentum值必须在0到1之间。

隐藏神经元的数量会影响训练网络的速度,网络可以解决的问题的复杂性,并有助于防止收敛于局部最小值。 您将需要至少具有与输出神经元一样多的隐藏神经元,并且可能需要更多。 大量隐藏神经元的缺点是需要存储大量权重。

初始随机权重应该相对较小。Sketch随附的配置中的InitialWeightMax值为.5。这会将所有初始权重设置为-0.5至.5之间,这是一个很好的起点。

这些参数的理想值根据训练数据的不同而有很大的不同,实际上没有直接的最佳选择方法。经验与反复试验相结合似乎是方法。

在配置部分中的最终值“成功”将在系统认为学习训练集时设置系统中错误的阈值级别。 它是一个非常小的数字,大于零。 这种类型的网络的本质是系统中的总误差将接近零,但从未真正达到零。

请注意,具有7个输入,8个隐藏的神经元和4个输出的示例网络大约可以在Arduino Uno的2K SRAM上运行。 不幸的是,如果您没有在Arduino上用完内存,则不会发出警告,Sketch的行为只会变得不稳定。 好消息是,SRAM分配大于2K的Arduino和Arduino兼容系统的列表一直在增长。 如果您成为一名成熟的神经网络实验者,您将有很多选择。

至此,我们已经为您提供了足够的基础,可以将示例网络的代码复制到您自己的计算机上,将其上传到Arduino,并尝试各种设置。

在配置部分之外,我们现在转到Sketch本身。 将神经网络实现为C程序的基本策略是建立一个数据数组框架,以保持权重并跟踪通过信号前馈和错误后馈累加的累加总数。 嵌套的FOR循环序列迭代这些数组,从而在执行反向传播算法时进行所需的各种计算。 数组,其他变量和常量的名称已与其网络中的功能相对应; 随着其余说明的进行,这些名称将变得更加清晰。

尽管代码不是绝对的初学者,但是如果您熟悉数组和循环的概念,那么您应该应该能够通读本文随附的Sketch并遵循逻辑。 这是一个高级细分:

  • 初始化数组。权重设置为随机数,并且两个保存反向传播中使用的更改值的其他数组设置为零。
  • 开始一个大循环,通过完整的训练数据集运行系统
  • 随机化训练集在每次迭代中运行的顺序,以减少局部最小值的振荡或收敛
  • 计算隐藏层激活,输出层激活和错误
  • 反向传播错误到隐藏层
  • 更新权重
  • 如果系统错误大于成功阈值,则运行训练数据的另一次迭代
  • 如果系统错误小于成功阈值,则中断,声明成功,然后将数据发送到串行终端
  • 每1000个周期将训练集的测试运行结果发送到串行终端

除了编程逻辑外,还有三个要理解的网络基本概念:激活函数,梯度适当和偏置。

激活函数根据馈入该神经元的加权连接的总和来计算该神经元的输出。 尽管有所不同,但此Sketch用了最常见的激活函数,称为“ Sigmoid函数”,这是因为其独特的形状,如下图所示。

该函数的关键特征是无论输入如何,输出都将落在0到1之间。此功能在编码神经网络时非常方便,因为神经元的输出始终可以表示为全开和全关l之间的范围。 激活函数可以在代码中采用通用形式的几个地方看到:

output = 1.0/(1.0 + exp(-Accum)) 

输出是表示要激活的神经元输出的数组变量,而Accum是该神经元加权输入的总数。 特定公式的复杂性并不重要,除了它们可以方便地产生S型输出。

梯度下降是反向传播的秘密武器。 这是一种数学方法,它使我们能够计算每个输出神经元的误差幅度,确定与该神经元的每个连接对误差的贡献程度,并对这些连接的权重进行增量调整,以系统地减少误差。

梯度下降的第一步是为每个神经元计算一个称为增量的值。 增量反映了误差的大小-神经元的目标值与其实际输出之间的差异越大,则增量越大。 在输出层,增量计算很简单:

delta = (target - actual) * actual * (1 - actual)

用代码中表示为

 OutputDelta[i] = (Target[p][i] - Output[i]) * Output[i] * (1.0 - Output[i]) ;

由于没有可测量的目标,因此计算隐藏层的增量将变得更加复杂。 取而代之的是,每个隐藏神经元的误差大小是根据权重与针对输出层计算的增量之间的关系得出的。 对于每个隐藏的神经元,代码逐步遍历所有输出连接,将权重乘以增量,并保持累加总数:

Accum += OutputWeights[i][j] * OutputDelta[j] ;

然后,通过将存储在Accum中的值替换为在第一次计算中看到的公式中的Target [p] [i]-Output [i]值来计算内层增量:

HiddenDelta[i] = Accum * Hidden[i] * (1.0 - Hidden[i]) ;

在计算出两层的增量值之后,下一步就是实际操作并调整权重。 在这里,我们看到学习率和动量的值如何改变权重的变化。 对于每个重量,要更改的量由以下公式确定:

change = (learning rate * weight * delta) + (momentum * previous change)

然后通过将旧权重添加到更改值中来找到新权重:

weight = weight + change

对于内层和隐藏层之间的权重,公式在代码中显示为:

ChangeHiddenWeights[j][i] = LearningRate * Input[p][j] * HiddenDelta[i] + Momentum * ChangeHiddenWeights[j][i];
  HiddenWeights[j][i] += ChangeHiddenWeights[j][i] ;

对于隐藏层和输出层之间的权重,公式在代码中显示为:

ChangeOutputWeights[j][i] = LearningRate * Hidden[j] * OutputDelta[i] + Momentum * ChangeOutputWeights[j][i] ;
  OutputWeights[j][i] += ChangeOutputWeights[j][i] ;

最后,我们开始偏差,这是一个相对简单的概念,但是当不理解它时,可能会使代码有些混乱。 输入层和隐藏层每个都包含一个始终触发的额外神经元(换句话说,它始终隐含激活为“ 1”)。 偏差值对网络有几个积极影响。 它增加了稳定性并扩展了可能的解决方案的数量。 最重要的是,它消除了所有输入均为零且因此没有信号通过网络传播的可能性。 如果查看包含权重和变化值的数组的声明,则会看到额外的神经元。 此外,您还将在处理激活和更新功能的嵌套循环中看到,不依赖输入值的偏向神经元有单独的计算。

详情参阅 - 亚图跨际

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BP神经网络反向传播算法是一种常用的人工神经网络算法。它是一种有监督学习算法,具有较好的非线性映射能力和逼近性能。在Arduino上实现BP神经网络反向传播算法需要以下步骤: 1. 搭建BP神经网络结构 BP神经网络结构包括输入层、隐层和输出层。输入层接收输入数据,隐层进行特征提取,输出层输出结果。在Arduino中,可以使用数组来模拟神经元和神经网络的连接。 2. 初始化权值和偏置 BP神经网络的训练过程需要初始化权值和偏置。权值和偏置的初始化可以使用随机数函数来实现。 3. 传播传播过程中,输入数据通过输入层传递到隐层,再通过隐层传递到输出层。每个神经元在接收到输入信号后,会根据自身的权值和偏置进行加权求和,并经过激活函数后输出。 4. 计算误差和损失函数 BP神经网络的训练过程是基于误差反向传播的,因此需要计算误差和损失函数。误差可以使用均方误差函数来计算。 5. 反向传播反向传播过程中,误差从输出层开始向传递,通过链式法则计算每层的误差和权值的梯度。然后根据梯度下降算法更新权值和偏置。 6. 更新权值和偏置 根据梯度下降算法更新权值和偏置,使得损失函数逐步减小,神经网络的训练效果逐步提高。 7. 迭代训练 重复进行传播、误差计算、反向传播和权值更新的过程,直到损失函数收敛或达到预设的训练次数为止。 下面是一个简单的Arduino代码实现BP神经网络反向传播算法: ```c++ #include <math.h> #define INPUT_NUM 2 #define HIDDEN_NUM 4 #define OUTPUT_NUM 1 #define LEARNING_RATE 0.5 #define EPOCHS 5000 float input[INPUT_NUM]; float hidden[HIDDEN_NUM]; float output[OUTPUT_NUM]; float target[OUTPUT_NUM]; float hidden_bias[HIDDEN_NUM]; float output_bias[OUTPUT_NUM]; float hidden_weights[INPUT_NUM][HIDDEN_NUM]; float output_weights[HIDDEN_NUM][OUTPUT_NUM]; float sigmoid(float x) { return 1.0 / (1.0 + exp(-x)); } void init_weights_bias() { for (int i = 0; i < HIDDEN_NUM; i++) { hidden_bias[i] = random(10) - 5; output_bias[0] = random(10) - 5; for (int j = 0; j < INPUT_NUM; j++) { hidden_weights[j][i] = random(10) - 5; } } for (int i = 0; i < OUTPUT_NUM; i++) { for (int j = 0; j < HIDDEN_NUM; j++) { output_weights[j][i] = random(10) - 5; } } } void forward() { for (int i = 0; i < HIDDEN_NUM; i++) { hidden[i] = 0; for (int j = 0; j < INPUT_NUM; j++) { hidden[i] += input[j] * hidden_weights[j][i]; } hidden[i] += hidden_bias[i]; hidden[i] = sigmoid(hidden[i]); } output[0] = 0; for (int i = 0; i < HIDDEN_NUM; i++) { output[0] += hidden[i] * output_weights[i][0]; } output[0] += output_bias[0]; output[0] = sigmoid(output[0]); } void backward() { float output_error = target[0] - output[0]; float output_delta = output_error * output[0] * (1 - output[0]); float hidden_error[HIDDEN_NUM]; float hidden_delta[HIDDEN_NUM]; for (int i = 0; i < HIDDEN_NUM; i++) { hidden_error[i] = output_delta * output_weights[i][0]; hidden_delta[i] = hidden_error[i] * hidden[i] * (1 - hidden[i]); } for (int i = 0; i < HIDDEN_NUM; i++) { for (int j = 0; j < OUTPUT_NUM; j++) { output_weights[i][j] += LEARNING_RATE * output_delta * hidden[i]; } } for (int i = 0; i < INPUT_NUM; i++) { for (int j = 0; j < HIDDEN_NUM; j++) { hidden_weights[i][j] += LEARNING_RATE * hidden_delta[j] * input[i]; } } for (int i = 0; i < HIDDEN_NUM; i++) { hidden_bias[i] += LEARNING_RATE * hidden_delta[i]; } output_bias[0] += LEARNING_RATE * output_delta; } void train() { for (int i = 0; i < EPOCHS; i++) { forward(); backward(); } } void setup() { Serial.begin(9600); randomSeed(analogRead(0)); init_weights_bias(); } void loop() { input[0] = random(10) / 10.0; input[1] = random(10) / 10.0; target[0] = input[0] * input[1]; train(); Serial.print("Input: "); Serial.print(input[0]); Serial.print(","); Serial.print(input[1]); Serial.print(" Target: "); Serial.print(target[0]); Serial.print(" Output: "); Serial.println(output[0]); delay(1000); } ``` 代码中使用了sigmoid函数作为激活函数,使用均方误差函数计算误差,使用随机数函数初始化权值和偏置,使用梯度下降算法更新权值和偏置。在Arduino上运行代码,可以模拟BP神经网络进行乘法运算的训练过程,并输出每次训练的输入、目标和输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值