我的学记|刘航宇的博客 - 💻电子&计算机
典型的SoC包括以下部分CPU与SRAM通信CPU与I2C接口通信AHB从接口AHB主接口APB桥APB Slave框图AXI总线AXI传输对比AMBA中AXI AHP APB比较典型的SoC包括以下部分• 一个或多个处理器内核，可以是MCU、MPU、数字信号处理器或专用指 令集处理器内核;• 存储器：可以是RAM、ROM、EEPROM或闪存;• 用于提供时间脉冲信号的振荡器和锁相环电路;• 由计数器和计时器、电源电路组成的外设;• 不同标准的连线接口，如USB、火线、以太网、通用异步收发;• 用于在数字信号和模拟信号之间转换的ADC/DAC;• 电压调理电路及稳压器。在外设内部，各组件通过芯片上的互联总线相互连接。ARM公司推出的 AMBA片上总线主要包括高性能系统总线AHB、通用系统总线ASB、外围互联总线APB、可拓展接口AXI。AHB主要针对高效率、高频宽及快速系统模块；ASB可用于某些高速且不必要使用AHB 总线的场合作为系统总线；APB 主要用于低速、低功率的外围，AXI在AMBA3.0协议中增加，可以用于ARM 和FPGA的高速数据交互。基于ARM的SoCCPU与SRAM通信CPU通过AHB对SRAM进行读写地址生成：CPU生成要访问的SRAM地址。信号发送：CPU通过AHB总线发送地址信息和读写请求信号。数据传输：读操作：CPU发送读请求后，SRAM将数据放在数据线上，CPU接收数据。写操作：CPU将数据放在数据线上，发送写请求，SRAM接收并存储数据。等待确认：操作完成后，SRAM可能通过AHB的握手信号通知CPU操作已完成。CPU与I2C接口通信CPU通过APB读取I2C的数据I2C是一种串行通信协议，通常用于低速外围设备与主控制器之间的通信。APB是一种用于连接低速外设的总线。以下是CPU通过APB读取I2C数据的一般步骤：初始化I2C外设：CPU通过APB发送指令初始化I2C外设，设置通信参数如时钟速率、地址等。发送启动条件：CPU通过I2C外设发送启动条件，开始通信。地址和读写位：CPU发送目标设备的I2C地址以及读写位（读操作为1，写操作为0）。数据传输：写操作：如果读写位为0，CPU通过APB发送数据到I2C外设，然后发送停止条件结束通信。读操作：如果读写位为1，CPU接收来自I2C外设的数据。数据接收：在读取操作中，CPU通过APB从I2C外设接收数据。发送停止条件：操作完成后，CPU发送停止条件，结束I2C通信。AHB从接口AHB主接口APB桥APB桥为AHB的一个从设备，但它在APB中是唯一的主设备，而APB中其它低速和低功率消耗的外围皆为APB桥的从设备。下图是 APB 桥的信号接口：APB桥将系统总线传送转换成APB方式的传送，它具备一些这些功能： 锁存地址，在传送过程中保持地址有效。锁存读写控制信号 对锁存的地址进行译码并产生选择信号PSELx，在传送过程中只有一个选择信号可以被激活。也就是选择出唯一一个APB从设备以进行读写动作. 写操作时: 负责将AHB送来的数据送上APB总线。 读操作时: 负责将APB的数据送上AHB系统总线。 产生一时序选通信号PENABLE作为数据传递时的启动信号APB Slave框图如前面所提及，APB 总线中除了 APB bridge 为 M 外，其它的外围皆为 S。因此，APB 从设备比 AHB从设备接口较为简单且非常具弹性: 例如 a. APB 少了仲裁器及复杂的译码电路，APB 进行写操作时，从设备可以决定: ： 在 PCLK 上升沿触发， 且 PSEL 为高时锁存数据 ：或在 PENABLE 上升沿, 且 PSEL 为高时锁存数据 b. PSELx，PADDR和PWRITE信号的组合可以决定哪个寄存器会被写操作更新。 c. 在读操作的时候，数据可以 PWRITE 在＝0，PSELx 和 PENABLE＝1 的时候被送到总线上，而PADDR 用于决定哪个寄存器会被读。AXI总线AXI的三种类型 AXI(AXI4-Full):用于高性能的存储器映射需求；（占用资源较多，传输大量数据） AXI4-Lite：简化版的AXI4接口；（占用资源较少，可用于低吞吐率的存储器的通信） AXI4-Stream：用于高速的流数据通信（不使用地址，适用于视频流）AXI的工作方式AXI4和AXI4-Lite包含5个独立的通道 读地址通道 读数据通道 写地址通道 写数据通道 写响应通道 AXI4：由于读写地址通道是分离的，所以支持双向同时传输；突发长度最大为256； AXI4-Lite：和AXI4比较类似，但是不支持突发传输； AXI4-Stream：和AXI4的写数据通道比较类似，只有一个单一的数据通道，突发长度不受限制；AXI传输对比Burst可以减少地址通道的交互，提升单笔传输的效率。Outstanding可以较少多笔传输之间的等待，提升多笔传输的效率。AMBA中AXI AHP APB比较
通讯等不确定性条件下设计无人机之间的安全距离
引用文章：简介1. 研究问题2. 分离原理介绍3. 安全半径设计4. 仿真及实验验证引用文章：Q. Quan, R. Fu and K. -Y. Cai, "How Far Two UAVs Should Be subject to Communication Uncertainties," in IEEE Transactions on Intelligent Transportation Systems, doi: 10.1109/TITS.2022.3213555.简介近年来，无人机技术的快速发展使得低空空域中无人机的数量爆炸增长。碰撞避免作为无人机应用的关键技术，近年来人们已经进行了大量研究，设计出各种方法使得无人机在飞行过程中与障碍物保持一定安全距离（本文将该距离称为无人机的 安全半径）。然而， 在碰撞避免相关研究中，不确定性往往是难以处理的问题。针对不确定性，主流的方法包括对不确定性进行预测，以及在闭环控制中给予补偿。然而，以上方法很依赖于预测及补偿的精准设计，设计不当将有可能导致控制器失效。本研究受地面交通中车辆间安全距离启发，基于对通信不确定性和无人机控制器性能的假设，提出了 安全半径设计和控制器设计的原则 （本文称为 分离原理 ）。进一步，安全半径具体通常通过经验预先设计，如果设计得过大或过小将分别影响无人机飞行的高效性及安全性。 如何根据无人机模型及通信不确定性确定其安全半径下界仍然是悬而未决的问题 。利用分离原理，本文研究了 设计阶段（无不确定性）和飞行阶段（受不确定性影响）的无人机安全半径设计 。最后，通过仿真和实验表明了所提方法的有效性。1. 研究问题本研究中，无人机与障碍物的运动模型均建模为多旋翼模型，换句话说，我们考虑的障碍物可以等效为另一架多旋翼飞行器。对于多旋翼飞行器而言，其可以获得自身以及障碍物的实时位置和速度。为简单描述起见，本文建模均为二维，类似的建模及分析方法可以扩展到三维情况。我们首先建立多旋翼的具体控制模型如下。其中多旋翼的位置和速度分别表示为 ，速度控制模型建立为一阶惯性环节，其时间常数 与无人机的机动性能相关，控制输入 为期望速度。在此基础上，本文定义一种新的滤波位置模型如下。多旋翼滤波位置的物理意义是根据多旋翼的当前位置、速度及机动能力，对其运动趋势预测。在此基础上，定义多旋翼的安全区域为以滤波位置为圆心的圆形区域，其半径称为安全半径 。对于障碍物，我们类似定义其滤波位置，以及以障碍物滤波位置为圆心的圆形障碍物区域，半径称为障碍物半径 。进一步，我们针对不确定性做了以下2点假设：（1）无人机和障碍物对自身的三维位置及三维速度估计均存在噪声；（2）无人机在获取障碍物状态信息时，存在通信延迟及丢包。该通信网络模型如下图所示。本研究中，丢包模型进一步建模为均值模型，且假设估计噪声、通信延迟、丢包均值模型误差均存在上界且上界已知。其数学定义如下：进一步，本文对无人机的控制器性能做出如下假设：（1）在无不确定性的理想状态下，无人机的控制器可以使无人机和障碍物之间的 真实距离 始终大于给定安全距离；（2）在存在以上不确定性的实际情况中，无人机的控制器可以使无人机和障碍物之间的 估计距离 始终保持给定安全距离。本研究的具体目标是在不确定性上界已知的条件下计算出无人机安全半径的下界。2. 分离原理介绍无人机在理想状态下设计的控制器中给定的安全半径值 ，在实际含有不确定性的环境中将转化为估计安全半径 ；直观上理解，将含有不确定性的无人机与障碍物之间的估计位置误差代替真实位置误差作为反馈时，控制器将难以维持给定的安全半径。本研究中， 分离原理具体研究的是不确定性在满足什么条件时无人机的控制器设计和安全半径设计过程可分离 ，也就是估计安全半径和安全半径相等， 成立。分离原理具体数学描述如下：分离原理中提出的三个条件包括一个充分必要条件(i)以及两个充分条件(ii)和(iii)。其中，条件(i)为理论推导，在实际中难以得到验证，通常可通过条件(ii)和(iii)进行验证。条件(ii)对无人机速度的限制较宽，该式在障碍物主动躲避无人机的条件下容易达成。反之，条件(iii)对无人机速度的限制较苛刻，要求无人机速度严格大于障碍物速度，适用于在障碍物不主动躲避无人机的情况下。以上两种情况的区别如下图所示。3. 安全半径设计根据上述分离原理的提出，在分离原理满足的前提下，基于无人机的安全半径模型和通信网络模型，可以进一步设计 合适大小的无人机安全半径 。如前文所述，安全半径设计过大会增加环境的冗余度，设计过小则无法保证飞行过程的安全。在本研究中，给出了在理想情况下以及实际情况下安全半径的下界，分别如下：4. 仿真及实验验证仿真及实验验证在仿真中，我们针对单障碍物、多非合作障碍物、多合作障碍物设计了三种场景来验证我们方法的有效性。在实验中，我们使用DJI tello无人机对以上情况进行测试，结果与我们的理论一致。实验视频：https://youtu.be/LkSDPFGa_1E论文地址https://rfly.buaa.edu.cn/pdfs/2022/How_far_two_UAVs_should_be_subject_to_communication_uncertainties.pdf
计算loss是会把所有loss层的loss相加。那么如何选取一个较为理想的medel？单独观察训练集loss曲线loss出现NAN联合观察loss曲线和accTensorFlow中loss与val_loss、accuracy和val_accuracy含义震荡修复计算loss是会把所有loss层的loss相加。从验证集误差是和测试集误差的角度分析其实你这个问题就是个伪命题，如果我们刻意的去在训练集上拟合模型，使其准确率达到很高的高度，或者说尽量在验证集合上表现的好，都是相悖的。因为我们不能为了某一特定数据集来刻意拟合，因为模型预测数据是不一定就在这个训练或者验证集合的空间中。还有，如果这个model预测集合acc20% 训练集合acc19% （即训练集精度低于测试集精度），那么这个模型肯定是不好的。还有一点需要注意，就是千万不能为了拟合测试集合而去更改模型，测试集合应该每次都有不同。那么如何选取一个较为理想的medel？首先，要有一个期望的准确率，通过不同模型的实验，找到最能接近的；然后，选定模型后进行参数调优；那么我们要尽可能的提高model的准确率，同时提高其泛化的能力，不能单一看某一指标，此时可参考 准确率、召回率、虚警率、F1Score等指标综合评判。或者采用多重验证随机划分训练、预测、验证集合，多次随机后找到最优参数。有时候训练集合误差很低，但是泛化能力极差，产生过拟合，有时候验证集合误差很低，但是可能验证集合无法代表所有的样本，有特殊性或者其他异常点较多。所以模型问题不能单一从你这两点来评判。一般而言，训练集loss < 验证集loss <测试集loss因为网络 [已见过] 所有训练集samples，故最低。而网络用验证集作为反馈来调节参数，相当于参考了验证集samples中的信息（间接 [已见过]）。又因为网络没有任何测试集的信息，所以测试结果一般而言最差。不过这都不是绝对的，有不符合这个一般现象的task，而我们不可以说哪种情况更“好”。多数情况验证集上错误率更低一点。因为是选择在验证集上准确率最高的模型来进行测试。考虑到数据的随机性，在验证集上准确率最高的模型在测试集上不一定是最高的，所以算出来的指标通常验证集会比测试集上好一点。但是实际情况下都有可能，特别是数据量不太大的时候。样本集合的数据也只是近似整体的分布，肯定会有波动。一个好的网络，二者的差距应该是很低的。但一般情况下因为网络不可避免地存在一定程度上的过拟合，所以肯定是train_loss低于test_lost，但如果低太多，就得考虑是过拟合的问题还是因为样本的特征空间不统一的问题。一般训练集不大时，最终训练的网络及容易过拟合，也就是说train-loss一定会收敛，但是test-loss不会收敛；训练时的loss会低于test的loss大概1~2个数量级，通常是10倍左右。{lamp/}单独观察训练集loss曲线 1）如果你的 learning_rate_policy 是 step 或者其他变化类型的话, loss 曲线可以帮助你选择一个比较合适的 stepsize； 2）如果loss曲线表现出线性（下降缓慢）表明学习率太低； 3）如果loss不再下降，表明学习率太高陷入局部最小值； 4）曲线的宽度和batch size有关，如果宽度太宽，说明相邻batch间的变化太大，应该减小batch size。可能导致不收敛的问题（如loss为87.3365，loss居高不下等）的解决方案 1）在caffe中可以在solver里面设置：debug_info: true,看看各个层的data和diff是什么值，一般这个时候那些值不是NAN（无效数字）就是INF（无穷大）； 2）检查数据标签是否从0开始并且连续； 3）把学习率base_lr调低； 4）数据问题，lmdb生成有误； 5）中间层没有归一化，导致经过几层后，输出的值已经很小了，这个时候再计算梯度就比较尴尬了，可以尝试在各个卷积层后加入BN层和SCALE层； 5）把base_lr调低，然后batchsize也调高； 6）把data层的输入图片进行归一化，就是从0-255归一化到0-1，使用的参数是： transform_param { scale: 0.00390625//像素归一化，1/255 } 7）网络参数太多，网络太深，删掉几层看看，可能因为数据少，需要减少中间层的num_output； 8）记得要shuffle数据，否则数据不够随机，几个batch之间的数据差异很小。loss出现NAN 1）观察loss值的趋势，如果迭代几次以后一直在增大，最后变成nan，那就是发散了，需要考虑减小训练速率，或者是调整其他参数。 2）数据不能太少，如果太少的话很容易发散" 3）Gradient Clipp 处理gradient之后往后传，一定程度上解决梯度爆炸问题。（但由于有了batch normalization，此方法用的不多） 4）原因可能是训练的时间不够长。 5）可以看一下训练时，网络参数的L2或L1联合观察loss曲线和acc 1）单独的 loss 曲线能提供的信息很少的,一般会结合测试机上的 accuracy 曲线来判断是否过拟合； 2）关键是要看你在测试集上的acc如何； 3）可以把accuracy和loss的曲线画出来，方便设定stepsize，一般在accuracy和loss都趋于平缓的时候就可以减小lr了； 4）看第一次test时（即iteration 0），loss和精度，如果太差，说明初始点的设置有问题 5）使用caffe时，train时，看见的loss是训练集的；accuracy才是测试集的 6）所谓的过拟合是：loss下降，accuracy也下降TensorFlow中loss与val_loss、accuracy和val_accuracy含义loss：训练集损失值accuracy:训练集准确率val_loss:测试集损失值val_accruacy:测试集准确率以下5种情况可供参考：train loss 不断下降，test loss不断下降，说明网络仍在学习;（最好的）train loss 不断下降，test loss趋于不变，说明网络过拟合;（max pool或者正则化）train loss 趋于不变，test loss不断下降，说明数据集100%有问题;（检查dataset）train loss 趋于不变，test loss趋于不变，说明学习遇到瓶颈，需要减小学习率或批量数目;（减少学习率）train loss 不断上升，test loss不断上升，说明网络结构设计不当，训练超参数设置不当，数据集经过清洗等问题。（最不好的情况）震荡修复验证集曲线震荡分析原因：训练的batch_size太小目前batch_size = 64，改成128：改成200：可见，增大batch_size 变大，震荡逐渐消失，同时在测试集的acc也提高了。batch_size为200时，训练集acc小于测试集，模型欠拟合，需要继续增大epoch。总结增大batchsize的好处有三点：1）内存的利用率提高了，大矩阵乘法的并行化效率提高。2）跑完一次epoch(全数据集)所需迭代次数减少，对于相同的数据量的处理速度进一步加快，但是达到相同精度所需要的epoch数量也越来越多。由于这两种因素的矛盾， batch_Size 增大到某个时候，达到时间上的最优。3）一定范围内，batchsize越大，其确定的下降方向就越准，引起训练震荡越小。盲目增大的坏处：1）当数据集太大时，内存撑不住。2）过大的batchsize的结果是网络很容易收敛到一些不好的局部最优点。3）batchsize增大到一定的程度，其确定的下降方向已经基本不再变化。4）太小的batch也存在一些问题，比如训练速度很慢，训练不容易收敛等。5）具体的batch size的选取和训练集的样本数目相关。
泛化能力，过拟合，欠拟合，不收敛，奥卡姆剃刀
泛化能力欠拟合过拟合与不收敛解决手段一、模型训练拟合的分类和表现二、欠拟合三、过拟合总结：泛化能力{message type="success" content="泛化能力（generalization ability）是指机器学习算法对新鲜样本的适应能力，简而言之是在原有的数据集上添加新的数据集，通过训练输出一个合理的结果。学习的目的是学到隐含在数据背后的规律，对具有同一规律的学习集以外的数据，经过训练的网络也能给出合适的输出，该能力称为泛化能力。"/}欠拟合过拟合与不收敛用比较直白的话来讲，就是通过数据训练学习的模型，拿到真实场景去试，这个模型到底行不行，如果达到了一定的要求和标准，它就是行，说明泛化能力好，如果表现很差，说明泛化能力就差。为了更好的理解泛化能力，这里引入三种现象，欠拟合、过拟合以及不收敛。{message type="success" content="欠拟合（under-fitting），是指模型拟合程度不高，数据距离拟合曲线较远，或指模型没有很好地捕捉到数据特征，不能够很好地拟合数据。即，在训练数据集上表现差，在测试集数据也表现差。过拟合（over-fitting），是指模型在训练集上表现很好，在测试集上效果差。不收敛（non-convergence）,指误差函数一直在振荡，不能趋近一个定值，没有找到局部或者全局最小值。"/}举个例子来说明下，好比高考数学考试，为了在高考能有个好成绩，高一到高三，好多人会采用“题海战术”来训练自己的做题能力，但高考试卷上的题，都是新题，几乎没有一模一样的题，学生们为了掌握解题规律就不停的刷题，希望最后自己碰到类似的题，能够举一反三，能学以致用，这种规律掌握的适用性，就是泛化能力。有的人对相似题型的解题规律掌握的很好，并且解题效果也很好，这种就是泛化能力强，这种同学往往数学成绩就好。有的同学成绩不好，就是泛化能力差，可能有三种情况{message type="success" content="1.做了不少题，但没有找到解题规律，不管碰到老题和新题都不会做，这种就是欠拟合。2.做了很多题，自认为做过的每一类题型的解题规律都掌握了，而且在之前“题海战术”的题目中，确实表现的很好，但是一碰到新的题目，完全就不会，或者做错，这种学生就是那种喜欢死记硬背的，这种就是过拟合。3.平常也不做题，然后每次一做题就瞎蒙，导致偶尔对，偶尔错，这种就是不收敛。"/}为了更直观展示，引用了几张图来说明，如下图所示，真实曲线是正弦曲线，蓝色的点是训练数据，红色的线为拟合曲线。解决手段在深度学习的模型建立过程中，一般都是用已经产生的数据训练，然后使用训练得到的模型去拟合未来的数据，借此来预测一些东西。在机器学习和深度学习的训练过程中，经常会出现欠拟合和过拟合的现象。训练一开始，模型通常会欠拟合，所以会对模型进行优化，等训练到一定程度后，就需要解决过拟合的问题了。一、模型训练拟合的分类和表现如何判断过拟合呢？我们在训练的时候会定义训练误差，验证集误差，测试集误差（即泛化误差）。训练误差总是减少的，而泛化误差一开始会减少，到了一定程度后不减少反而开始增加，这时候便出现了过拟合的现象。如下图，直观理解，欠拟合就是还没有学习到数据的特征，还有待继续学习，所以此时判断的不准确；而过拟合则是学习的太彻底，以至于把数据的一些不需要的局部特征或者噪声所带来的特征都给学习到了，所以在测试的时候泛化误差也不佳。从方差和偏差的角度来说，欠拟合就是在训练集上高方差，高偏差，过拟合就是高方差，低偏差。为了更加直观，我们看下面的图对比上图，图一的拟合并没有把大体的规律给拟合出来，拟合效果不好，这个就是欠拟合，还需要继续学习规律，此时模型简单；图三的拟合过于复杂，拟合的过于细致，以至于拟合了一些没有必要的东西，这样在训练集上效果很好，但放到测试集和验证集就会不好。图二是最好的，把数据的规律拟合出来了，也没有更复杂，更换数据集后也不会效果很差。在上面的拟合函数中，可以想到，图三过拟合的拟合函数肯定是一个高次函数，其参数个数肯定比图二多，可以说图三的拟合函数比图二要大，模型更加复杂。这也是过拟合的一个判断经验，模型是否过于复杂。另外针对图三，我们把一些高次变量对应的参数值变小，也就相当于把模型变简单了。从这个角度上讲，可以减少参数值，也就是一般模型过拟合，参数值整体比较大。从模型复杂性上讲，可以是： ——模型的参数个数； ——模型的参数值的大小。 个数越多，参数值越大，模型越复杂。二、欠拟合欠拟合的表现有什么方法来判断模型是否欠拟合呢？其实一般都是依靠模型在训练集和验证集上的表现，有一个大概的判断就行了。如果要有一个具体的方法，可以参考机器学中，学习曲线来判断模型是否过拟合，如下图：欠拟合的解决方案（1）增加数据特征：欠拟合是由于学习不足导致的，可以考虑添加特征，从数据中挖掘更多的特征，有时候嗨需要对特征进行变换，使用组合特征和高次特征；（2）使用更高级的模型：模型简单也会导致欠拟合，即模型参数过少，结构过于简单，例如线性模型只能拟合一次函数的数据。尝试使用更高级的模型有助于解决欠拟合，增加神经网络的层数，增加参数个数，或者使用更高级的方法；（3）减少正则化参数：正则化参数是用来防止过拟合的，出现欠拟合的情况就要考虑减少正则化参数。三、过拟合过拟合的定义模型在训练集上表现好，但在测试集和验证集上表现很差，这就是过拟合过拟合的原因（1）数据量太小这是很容易产生过拟合的原因。设想我们有一组数据很好的满足了三次函数的规律，但我们只取了一小部分数据进行训练，那么得到的模型很可能是一个线性函数，把这个线性函数用于测试集上，可想而知肯定效果很差。（此时训练集上效果好，测试集效果差）（2）训练集和验证集分布不一致这也是很大一个原因。训练集上训练出来的模型适合训练集，当把模型应用到一个不一样分布的数据集上，效果肯定大打折扣，这个是显而易见的。（3）网络模型过于复杂选择模型算法时，选择了一个复杂度很高的模型，然而数据的规律是很简单的，复杂的模型反而不适用了。（4）数据质量很差数据有很多噪声，模型在学习的时候，肯定也会把噪声规律学习到，从而减少了一般性的规律。这个时候模型预测效果也不好。（5）过度训练这是同第四个相联系的，只要模型训练时间足够长，那么模型肯定会把一些噪声隐含的规律学习到，这时候降低模型的性能也是显而易见的。解决方法（1）降低模型复杂度处理过拟合的第一步就是降低模型复杂度。为了降低复杂度，我们可以简单地移除层或者减少神经元的数量使得网络规模变小。与此同时，计算神经网络中不同层的输入和输出维度也十分重要。虽然移除层的数量或神经网络的规模并无通用的规定，但如果你的神经网络发生了过拟合，就尝试缩小它的规模。（2）数据集扩增在数据挖掘领域流行着这样的一句话，“有时候往往拥有更多的数据胜过一个好的模型”。因为我们在使用训练数据训练模型，通过这个模型对将来的数据进行拟合，而在这之间又一个假设便是，训练数据与将来的数据是独立同分布的。即使用当前的训练数据来对将来的数据进行估计与模拟，而更多的数据往往估计与模拟地更准确。因此，更多的数据有时候更优秀。但是往往条件有限，如人力物力财力的不足，而不能收集到更多的数据，如在进行分类的任务中，需要对数据进行打标，并且很多情况下都是人工得进行打标，因此一旦需要打标的数据量过多，就会导致效率低下以及可能出错的情况。所以，往往在这时候，需要采取一些计算的方式与策略在已有的数据集上进行手脚，以得到更多的数据。通俗的讲，数据机扩增即需要得到更多的符合要求的数据，即和已有的数据是独立同分布的，或者近似独立同分布的。一般有以下方法： ——从数据源头采集更多数据 ——复制原有数据加上噪声 ——重采样 ——根据当前数据集估计数据分布参数，利用该分布产生更多数据（3）数据增强使用数据增强可以生成多幅相似图像。这可以帮助我们增加数据集从而减少过拟合。因为随着数据量的增加，模型无法过拟合所有样本，因此不得不进行泛化。计算机视觉领域通常的做法有：翻转，平移，旋转，缩放，改变亮度，添加噪声等。（4）正则化正则化是指在进行目标函数或者代价函数优化时，在目标函数或者代价函数后面加上一个正则项，一般有L1正则和L2正则等。L1惩罚项的目的是使权重绝对值最小化，公式如下：L1惩罚项的目的是使权重的平方最小化，公式如下：下面对两种正则化方法进行了比较：如果数据过于复杂以致没有办法进行准确的建模，那么L2是更好的选择，因为它能够学习数据中呈现的内在模式。而当数据足够简单，可以精确建模的话，L1更合适，对于我遇到的大多数计算机视觉问题，L2正则化几乎总是可以给出最好的结果。然而L1不容易受到离群值的影响。所以正确的正则化选项取决于我们想要解决的问题。总结：正则项是为了降低模型的复杂度，从而避免模型过分拟合训练数据，包括噪声与异常点。从另一个角度讲，正则化即是假设模型参数服从先验概率，即为模型参数添加先验，只是不同的正则化方式的先验分布是不一样的。这样就规定了参数的分布，使得模型的复杂度降低（试想一下，限定条件多了，是不是模型的复杂度就降低了呢），这样模型对于噪声和异常点的抗干扰性的能力增强，从而提高模型的泛化能力。还有个解释，从贝叶斯学派来看，加了先验，在数据少的时候，先验知识可以防止过拟合；从频率学派来看，正则项限定了参数的取值，从而提高了模型的稳定性，而稳定性强的模型不会过拟合，即控制模型空间。另外一个角度，过拟合从直观上理解便是，在对训练数据进行拟合时，需要照顾到每个点，从而使得拟合函数波动性非常大，即方差大。在某些小区间里，函数值的变化很剧烈，意味着函数在某些小区间的导数值的绝对值非常大，由于自变量的值在给定的训练数据集中是一定的，因为只有系数足够大，才能保证导数的绝对值足够大，如下图：另一个解释，规则化项的引入，在训练（最小化cost）的过程中，当某一维的特征所对应的权重过大时，而此时模型的预测和真实数据之间的距离很小，通过规则化项就可以使整体的cost取较大的值，从而，在训练的过程中避免了去选择了那些某一维（或几维）特征权重过大的情况，即过分依赖某一维（或几维）的特征。L1和L2的区别是，L1正则是拉普拉斯先验，L2正则则是高斯先验。它们都是服从均值为0，协方差为1/λ。当λ=0，即没有先验，没有正则项，则相当于先验分布具有无穷大的协方差，那么这个先验约束则会非常弱，模型为了拟合拟合所有的训练集数据，参数可以变得任意大从而使得模型不稳定，即方差大而偏差小。λ越大，表明先验分布协方差越小，偏差越大，模型越稳定。即，加入正则项是在偏差bias与方差variance之间做平衡tradeoff。下图即为L2与L1正则的区别：上图中的模型是线性回归，有两个特征，要优化的参数分别是w1和w2，左图的正则化是L2，右图是L1。蓝色线就是优化过程中遇到的等高线，一圈代表一个目标函数值，圆心就是样本观测值（假设一个样本），半径就是误差值，受限条件就是红色边界（就是正则化那部分），二者相交处，才是最优参数。可见右边的最优参数只可能在坐标轴上，所以就会出现0权重参数，使得模型稀疏。其实拉普拉斯分布和高斯分布是数学家从试验中误差服从什么分布研究得出的。一般直观上的认识是服从均值为0的对称分布，并且误差大的概率低，误差小的概率高，因为拉普拉斯使用拉普拉斯分布对误差的分布进行拟合，如下图：而拉普拉斯在最高点，即自变量为0处不可导，因为不便于计算，于是高斯在这基础上使用高斯分布对其进行拟合，如下图：（5）dropout正则时通过再代价函数后面加上正则项来防止过拟合的。而在神经网络中，有一种方法时通过修改神经网络本身结构实现的，其名为dropout。该方法是对网络进行训练时用的一种技巧，对于如下的三层人工神经网络：对于上图所示的网络，在训练开始时，随即删除一些（可自己设定概率）隐藏层神经元，即认为这些神经元不存在，同时保持输入层和输出层的个数不变，这样便得到如下的ANN：然后按照BP学习算法对ANN中的参数进行学习更新（虚线链接的单元不更新，因为认为这些连接元被临时删除了）。这样一次迭代更新便完成了，下一次迭代中，同样随机删除一些神经元，与上次不一样，做随机选择，这样一直进行，直至训练结束。这种技术被证明可以减少很多问题的过拟合，这些问题包括图像分类，图像切割，词嵌入，语义匹配等问题。（6）早停对模型的训练即是对模型的参数进行更新的过程，这个参数学习的过程往往会用到一些迭代方法，如梯度下降（Gradient descent）学习算法。Early stopping一种迭代次数截断的方法来防止过拟合的方法，即在模型对训练数据集迭代收敛之前停止迭代来防止过拟合。Early stopping方法的具体做法是，在每一个Epoch结束时（一个Epoch集为对所有的训练数据的一轮遍历）计算验证集的正确率，当正确率不再提高时，就停止训练。这种做法很符合直观感受，因为正确率都不在提高了，再继续训练也是无益的，只会提高训练的时间。如下图，在几次迭代后，即使训练误差仍然在减少，但测验误差已经开始增加了。那么该做法的一个重点便是怎样才认为验证准确率validation accurary不再提高了呢？并不是说验证准确率validation accurary一降下来便认为不再提高了，因为可能经过这个Epoch后，正确率降低了，但是随后的Epoch又让正确率又上去了，所以不能根据一两次的连续降低就判断不再提高。一般的做法是，在训练的过程中，记录到目前为止最好的验证准确率validation accurary，当连续10次没达到最佳正确率时，认为不再提高了，此时便可以停止迭代。这种策略也称为“No-improvement-in-n”，n即Epoch的次数，可以根据实际情况取，如10、20、30……（7）重新清洗数据把明显异常的数据剔除。（8）使用集成学习方法把多个模型集成在一起，降低单个模型的过拟合风险。
1.标签 Label2.特征 Feature3.样本 Example3.1有标签样本(labeled):3.2无标签样本(unlabeled)：4.模型 Model5.回归 Regression6.分类 Classification7.机器学习算法地图1.标签 Label标签：所预测的东东实际是什么（可理解为结论），如线性回归中的 y 变量，如分类问题中图片中是猫是狗（或图片中狗的种类）、房子未来的价格、音频中的单词等等任何事物，都属于Label。（如一组图片，已经表明了哪些是狗，哪些是猫，这里Label就是分类问题中每一个类）2.特征 Feature特征是事物固有属性，可理解为做出某个判断的依据，如人的特征有长相、衣服、行为动作等等，一个事物可以有N多特征，这些组成了事物的特性，作为机器学习中识别、学习的基本依据。特征是机器学习模型的输入变量。如线性回归中的 x 变量。例如在垃圾邮件分类问题中，特征可能包括：电子邮件中是否包含 “ 广告、贷款、交易” 等短语电子邮件文本中的字词发件人的地址发送电子邮件的时段其中机器学习重要步骤：特征提取就是通过多种方式，对数据的特征数据进行提取。一般，特征数据越多，训练的机器学习模型就会越精确，但处理难度也越大。3.样本 Example样本是指一组或几组数据，样本一般作为训练数据来训练模型样本分为以下两类：有标签样本无标签样本3.1有标签样本(labeled):同时包含特征和标签在监督学习中利用数据做训练时，有标签数据/样本（Labeled data）或叫有/无标记数据，就是指有没有将数据归为要预测的类别。例如以下房价数据集：其中包含特征：卧室数量等，最右边一列是标签：房价中间值（注，因为该问题要预测未来房价走势，所以Label就是某条房屋数据中的房价）3.2无标签样本(unlabeled)：包含特征，但不包含标签如以下数据集： 去掉了Label，但是一样有用（如用在测试训练后的模型，即训练好模型后，输入该数据，那到预测后的房价与原标签进行比较，得到模型误差）4.模型 Model模型定义了特征与标签之间的关系，就是我们机器学习的一组数据关系表示，也是我们学习机器学习的核心例如，垃圾邮件检测模型可能会将某些特征与“垃圾邮件”紧密联系起来。模型生命周期的两个重要阶段：训练 Training是指创建或学习模型。也就是说，向模型展示有标签样本，让模型逐渐学习特征与标签之间的关系。训练模型表示通过有标签样本来学习（确定）所有权重和偏差的理想值推断 Inference是指将训练后的模型应用于无标签样本。也就是说，使用经过训练的模型做出有用的预测 (y’)。例如，模型训练好后，就可以使用模型进行Inference ，可以针对新的无标签样本预测房价medianHouseValue。5.回归 Regression回归就是我们数学学习的线性方程，是一种经典函数逼近算法。在机器学习中，就是根据数据集，建立一个线性方程组，能够无线逼近数据集中的数据点，是一种基于已有数据关系实现预测的算法。回归模型可预测连续值（线性）例如，回归模型做出的预测可回答如下问题：某小区房价的趋势？用户点击此广告的概率是多少？当机器学习模型最终目标（模型输出）是求一个具体数值时，例如房价的模型输出为25000，则大多数可以通过回归问题来解决。线性回归的好处在与模型简单，计算速度快，方便应用在分布式系统对大数据进行处理。线性回归还有个姐妹：逻辑回归（Logistic Regression），主要应用在分类领域6.分类 Classification顾名思义，分类模型可用来预测离散值例如，分类模型做出的预测可回答如下问题：是/否问题，某个指定电子邮件是垃圾邮件还是非垃圾邮件？图片是动物还是人？垃圾分类当机器学习模型最终目标（模型输出）是布尔或一定范围的数时，例如判断一张图片是不是人，模型输出0/1：0不是，1是；又例如垃圾分类，模型输出1-10之间的整数，1代表生活垃圾，2代表厨余垃圾。。等等，这类需求则大多数可以通过分类问题来解决。7.机器学习算法地图机器学习算法多种多样，许多情况下，建模和算法设计是Designer所选择的，具体采用哪种算法也没有一定要求，根据实际具体问题具体分析。
华为C++算法-识别有效的IP地址和掩码并进行分类统计
问题注意：输入描述：输出描述：示例需要注意的细节思路具体实现代码问题请解析IP地址和对应的掩码，进行分类识别。要求按照A/B/C/D/E类地址归类，不合法的地址和掩码单独归类。所有的IP地址划分为 A,B,C,D,E五类A类地址从1.0.0.0到126.255.255.255;B类地址从128.0.0.0到191.255.255.255;C类地址从192.0.0.0到223.255.255.255;D类地址从224.0.0.0到239.255.255.255；E类地址从240.0.0.0到255.255.255.255私网IP范围是：从10.0.0.0到10.255.255.255从172.16.0.0到172.31.255.255从192.168.0.0到192.168.255.255子网掩码为二进制下前面是连续的1，然后全是0。（例如：255.255.255.32就是一个非法的掩码）（注意二进制下全是1或者全是0均为非法子网掩码）注意：类似于【0...】和【127...】的IP地址不属于上述输入的任意一类，也不属于不合法ip地址，计数时请忽略私有IP地址和A,B,C,D,E类地址是不冲突的输入描述：多行字符串。每行一个IP地址和掩码，用~隔开。输出描述：统计A、B、C、D、E、错误IP地址或错误掩码、私有IP的个数，之间以空格隔开。示例{message type="success" content="输入：10.70.44.68~255.254.255.01.0.0.1~255.0.0.0192.168.0.2~255.255.255.019..0.~255.255.255.0输出：1 0 1 0 0 2 1说明：10.70.44.68~255.254.255.0的子网掩码非法，19..0.~255.255.255.0的IP地址非法，所以错误IP地址或错误掩码的计数为2；1.0.0.1~255.0.0.0是无误的A类地址；192.168.0.2~255.255.255.0是无误的C类地址且是私有IP；所以最终的结果为1 0 1 0 0 2 1 "/}示例2{message type="success" content="输入：0.201.56.50~255.255.111.255127.201.56.50~255.255.111.255输出：0 0 0 0 0 0 0说明：类似于【0...】和【127...】的IP地址不属于上述输入的任意一类，也不属于不合法ip地址，计数时请忽略 "/}需要注意的细节类似于【0...】和【127...】的IP地址不属于上述输入的任意一类，也不属于不合法ip地址，计数时可以忽略私有IP地址和A,B,C,D,E类地址是不冲突的，也就是说需要同时+1如果子网掩码是非法的，则不再需要查看IP地址全零【0.0.0.0】或者全一【255.255.255.255】的子网掩码也是非法的思路按行读取输入，根据字符‘~’ 将IP地址与子网掩码分开查看子网掩码是否合法。合法，则继续检查IP地址非法，则相应统计项+1，继续下一行的读入查看IP地址是否合法合法，查看IP地址属于哪一类，是否是私有ip地址；相应统计项+1非法，相应统计项+1具体实现判断IP地址是否合法，如果满足下列条件之一即为非法地址数字段数不为4存在空段，即【192..1.0】这种某个段的数字大于255判断子网掩码是否合法，如果满足下列条件之一即为非法掩码不是一个合格的IP地址在二进制下，不满足前面连续是1，然后全是0在二进制下，全为0或全为1如何判断一个掩码地址是不是满足前面连续是1，然后全是0？将掩码地址转换为32位无符号整型，假设这个数为b。如果此时b为0，则为非法掩码将b按位取反后+1。如果此时b为1，则b原来是二进制全1，非法掩码如果b和b-1做按位与运算后为0，则说明是合法掩码，否则为非法掩码代码注意getline函数可以指定分割字符串的字符// 引入输入输出流、字符串、字符串流和向量等头文件 #include<iostream> #include<string> #include<sstream> #include<vector> // 使用标准命名空间 using namespace std; // 定义一个函数，判断一个字符串是否是合法的IP地址 bool judge_ip(string ip){ // 定义一个整数变量，记录IP地址的段数 int j = 0; // 定义一个字符串流对象，用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量，用于存储IP地址的每一段 string seg; // 使用循环，以'.'为分隔符，获取IP地址的每一段 while(getline(iss,seg,'.')) // 如果段数加一大于4，或者该段为空，或者该段的数值大于255，说明不是合法的IP地址，返回false if(++j > 4 || seg.empty() || stoi(seg) > 255) return false; // 如果循环结束后，段数等于4，说明是合法的IP地址，返回true return j == 4; } // 定义一个函数，判断一个字符串是否是私有的IP地址 bool is_private(string ip){ // 定义一个字符串流对象，用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量，用于存储IP地址的每一段 string seg; // 定义一个整数向量，用于存储IP地址的每一段的数值 vector<int> v; // 使用循环，以'.'为分隔符，获取IP地址的每一段，并将其转换为整数，存入向量中 while(getline(iss,seg,'.')) v.push_back(stoi(seg)); // 如果IP地址的第一段等于10，说明是私有的IP地址，返回true if(v[0] == 10) return true; // 如果IP地址的第一段等于172，并且第二段在16到31之间，说明是私有的IP地址，返回true if(v[0] == 172 && (v[1] >= 16 && v[1] <= 31)) return true; // 如果IP地址的第一段等于192，并且第二段等于168，说明是私有的IP地址，返回true if(v[0] == 192 && v[1] == 168) return true; // 如果以上条件都不满足，说明不是私有的IP地址，返回false return false; } // 定义一个函数，判断一个字符串是否是合法的子网掩码 bool is_mask(string ip){ // 定义一个字符串流对象，用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量，用于存储IP地址的每一段 string seg; // 定义一个无符号整数变量，用于存储IP地址的二进制表示 unsigned b = 0; // 使用循环，以'.'为分隔符，获取IP地址的每一段，并将其转换为整数，左移8位后与b进行按位或运算，得到IP地址的二进制表示 while(getline(iss,seg,'.')) b = (b << 8) + stoi(seg); // 如果b等于0，说明不是合法的子网掩码，返回false if(!b) return false; // 将b按位取反后加一，得到b的补码 b = ~b + 1; // 如果b等于1，说明不是合法的子网掩码，返回false if(b == 1) return false; // 如果b与b减一进行按位与运算，结果等于0，说明b只有一个1，说明是合法的子网掩码，返回true if((b & (b-1)) == 0) return true; // 如果以上条件都不满足，说明不是合法的子网掩码，返回false return false; } // 定义主函数 int main(){ // 定义一个字符串变量，用于存储输入的IP地址和子网掩码 string input; // 定义七个整数变量，用于统计A、B、C、D、E类地址、错误地址和私有地址的个数 int a = 0,b = 0,c = 0,d = 0,e = 0,err = 0,p = 0; // 使用循环，读取输入的IP地址和子网掩码，直到输入结束 while(cin >> input){ // 定义一个字符串流对象，用于分割IP地址和子网掩码 istringstream is(input); // 定义一个字符串变量，用于存储IP地址或子网掩码 string add; // 定义一个字符串向量，用于存储IP地址和子网掩码 vector<string> v; // 使用循环，以'~'为分隔符，获取IP地址和子网掩码，并存入向量中 while(getline(is,add,'~')) v.push_back(add); // 如果IP地址或子网掩码不合法，错误地址的个数加一 if(!judge_ip(v[1]) || !is_mask(v[1])) err++; else{ // 如果IP地址不合法，错误地址的个数加一 if(!judge_ip(v[0])) err++; else{ // 获取IP地址的第一段的数值 int first = stoi(v[0].substr(0,v[0].find_first_of('.'))); // 如果IP地址是私有的，私有地址的个数加一 if(is_private(v[0])) p++; // 根据IP地址的第一段的数值，判断IP地址的类别，并相应的类别的个数加一 if(first > 0 && first <127) a++; else if(first > 127 && first <192) b++; else if(first > 191 && first <224) c++; else if(first > 223 && first <240) d++; else if(first > 239 && first <256) e++; } } } // 输出A、B、C、D、E类地址、错误地址和私有地址的个数 cout << a << " " << b << " " << c << " " << d << " " << e << " " << err << " " << p << endl; // 返回0，表示程序正常结束 return 0; }
ZYNQ是什么？ZYNQ为什么厉害ZYNQ的结构开发工具ZYNQ开发流程ZYNQ是什么？这是一款由Xilinx公司开发的集成了ARM处理器和FPGA可编程逻辑的片上系统（SoC）芯片。ZYNQ7000有多个型号，根据处理器核心数和FPGA系列的不同，可以应用于多种领域，如图像处理，通信，嵌入式系统等。ZYNQ中国人读法 “zingke”、“任克”，“Soc”，英文全称叫 System on one Chip ，也就是片上系统的意思。没有微机基础的同学可能不明白什么叫Soc，但是你可以细细琢磨一下，我们的手机和台式电脑的不同，你就可以理解Soc的内含了。传统计算机是将CPU，内存，GPU，南北桥焊接在印刷电路板上，各个组件之间是分立的。但是Soc则将CPU和各种外设集中到一块芯片上，集合成一个系统，因此像手机这种使用了Soc芯片的这种微机可以做的很轻薄，我们可以说，Soc是未来微机发展的一个趋势，我之前遇见过的像什么全志的A33就是典型的Soc。ZYNQ为什么厉害就在于它是一块可编程的Soc。其内部往往有处理器硬核和一些定制外设，并且外设当中有一个很厉害的玩意：PL，即可编程逻辑模块，也就是我们一般意义上的FPGA，所以简单理解ZYNQ就是“ 单片机 + FPGA “，它既可以执行代码程序，也可以实现FPGA。因此我们设计ZYNQ就是在做Soc设计。ZYNQ的结构我们先来开一下简化版的模型上面的模型细致低展开后就是下图的样子： 图是 ZYNQ 7000的结构图，大体分为PS（Processing System）和 PL（Programmable Logic）两部分，其中的PS部分主要是由双核APU和外围的一些外设组成，说实话很像单片机的结构，而外围的PL则类似FPGA，并且两者通过AXI接口进行互联以实现功能.重点介绍一下APU，应用处理单元：Application Processing Unit，位于PS（processing system）中，包括一个单核或者双核的cortex-A9处理器，处理器连接一个512KB的共享L2cache，每个处理器都有一个32KB的高速L1 cache，A9支持虚拟内存和32bit arm 指令。APU中的A9处理器由可配置的MP组成，MP包含SCU（snoop control unit：监控控制单元）单元，这个单元主要负责获取两个处理器的L1 cache和ACP（accelerator coherency port：加速器相关接口） PL的一致性。应用单元还有一个低延迟的片上memory，与L2 cache并行的，ACP（加速器接口）是PL与APU通信接口，该接口是PL作为主机的AXI协议的接口，最多支持64bit位宽，PL通过ACP接口访问L2 cache 和片上memory，同时保持和L1 cache的内存一致性。L2 cache 可以访问 DDR 控制，这个ddr 控制器是专用的，大大降低内存读写的延迟APU 还包括一个32bit的看门狗，一个64bit的全局定时器，APU 架构图如下所示：开发工具在Vivado 19.2之前，我们开发Zynq需要三样必须的软件：VivadoSDKPetaLinux其中Vivado用来开发硬件平台，SDK开发软件，PetaLinux则制作配套的Linux系统。可能有些人还有用到HLS ，即VIvado HLS 或者Vitis HLS；其中Vivado HLS 2020.1将是Vivado HLS的最后一个版本，取而代之的是VitisHLS。到了Vivado 19.2之后，事情发生了变化。为了方便大家理解，我愿意称之这些软件成了为Vitis 家族的各个部分，原来的SDK被Vitis IDE取代，Vivado导出的 .hdf 文件被 .xsa文件代替，用来给vitis平台使用。因此我们需要的开发Zynq 最基本的软件变成了VivadoVitis IDEPetaLinux各软件发挥的作用和之前的差不多，不过除了上面提到的四款软件外，Vitis家族还有 Vitis AI 等组件，他们共同组成了所谓的“Vitis™ Unified Software Platform ”，从发展趋势来看，这些开发软件应该会逐步的统一，入门的同学也不会再一头雾水地纠结 Vitis 和 Vivado 的区别和联系了。ZYNQ开发流程ZYNQ类似于一个 单片机 + FPGA的结构，其实我觉得如果大家接触过一些 Soc就会更好地理解ZYNQ的作用，就例如全志A33这块Soc，它是一块ASIC，不可以通过编程来对芯片的硬件进行重设计的。 我们可以看到，灰色部分的外设都是固定的，像什么摄像头接口，什么视频接口都是设计好的，定制化的好处就使得总体比较高效，制造成本也低；但是如果我要运用到其它场景下，比如说我需要多个摄像头，那这块芯片就不再适合了（硬件控制的上限就是前后两颗摄像头）而ZYNQ的意义相当于只给你定制的蓝色部分，也就是处理器内核，灰色的部分都可以通过FPGA实现，这让电子工程师们可以快速开发出各种各样有针对性的Soc；当然了，看过我第一篇博客的同学都知道，其实固定的硬核不止只有处理器内核，其实还有串口和内存控制器之类的外设，这其实是追寻一种固定和变化之间的平衡。咱们把话说回ZYNQ的开发上来。ZYNQ的开发流程分为硬件和软件两部分，在SDK之前的属于硬件开发，也就是我们常说的PL部分的开发，而SDK后就属于软件部分的开发了，类似单片机，属于PS部分。当然现在最新的Vitis IDE已经取代了SDK，所以后半部分一般在SDK中进行。PL部分的开发包括对 嵌入式最小系统的构建，以及FPGA外设的设计两个方面。我觉得要转变的一个思维是，我们现在不是在开发一个什么SDRAM控制器，什么IIC协议控制器，我们在开发的是一个小型的微机系统！因此嵌入式最小系统的设计是我们的核心。首先，在IP INTEGRATOR中我们要创建BLOCK DESIGN。IP是用来进行 Embedded System Design ，也就是咱们常说的嵌入式系统设计。也就是咱们上面说的嵌入式最小系统的设计。大家可以看到，一个最小的系统其实不需要PL参与的，PL可以作为PS的一个外设使用，或者是自己做自己的事情，仅仅作为一个PL工作。既然是外设，当然是可用可不用的，毕竟咱们有好多的外设可以在Block Design 中直接配置使用，即下图绿色部分。配置好嵌入式系统后，咱们根据需要进行PL部分的设计。这里涉及一个问题，那就是PS和PL之间的数据传输方式有哪些：中断IO方式：MIO EMIO GPIOBRAM或FIFO或EMIFAXI DMA：PS通过AXI-lite向AXI DMA发送指令，AXI DMA通过HP通路和DDR交换数据，PL通过AXI-S读写DMA的数据。等等。。。可以看出，其实两个部分的交互方式还是很多的，以后咱们遇到一个说一个。在Vivado端完成对嵌入式系统的设计后，我们就要进入Vitis IDE 端进行软件的开发。Vitis IDE简单来说流程一般是：新建一个工程，选择Platform ，也就是我们之前在Vivado中生成的 XSA文件，然后添加文件，进行开发。我相信使用过Keil 5的同学们应该心中对文件目录结构应该更胸有成竹，Src文件夹中存放的是源文件。代码编写完之后是编译，编译完就是下载了。不过这里要注意以下，如果我们使用了PL的资源，那么在下载软件编译生成的 elf 文件之前，需要先下载硬件设计过程中生成的 bitstream 文件，对 PL 部分进行配置。最后就是验证工作了，上述的流程是普通的ZYNQ开发流程；玩的花一点的同学可能是直接上Linux操作系统，这部分等后面我接触到了再说吧！其实我觉得ZYNQ入门简单，精通的话需要大量的知识储备，但也不是不可能，开发ZYNQ相比于做单片机开发肯定路子会更广一些，向上可以做IC设计，向下嵌入式、单片机什么的工作也能胜任。
机器学习/深度学习-10个激活函数详解
综述1. Sigmoid 激活函数2. Tanh / 双曲正切激活函数3. ReLU 激活函数4. Leaky ReLU5. ELU6. PReLU（Parametric ReLU）7. Softmax8. Swish9. Maxout10. Softplus综述激活函数（Activation Function）是一种添加到人工神经网络中的函数，旨在帮助网络学习数据中的复杂模式。类似于人类大脑中基于神经元的模型，激活函数最终决定了要发射给下一个神经元的内容。在人工神经网络中，一个节点的激活函数定义了该节点在给定的输入或输入集合下的输出。标准的计算机芯片电路可以看作是根据输入得到开（1）或关（0）输出的数字电路激活函数。因此，激活函数是确定神经网络输出的数学方程式，本文概述了深度学习中常见的十种激活函数及其优缺点。首先我们来了解一下人工神经元的工作原理，大致如下：上述过程的数学可视化过程如下图所示：1. Sigmoid 激活函数Sigmoid 函数的图像看起来像一个 S 形曲线。函数表达式如下：$$\[\mathrm{f(z)=1/(1+e^{\wedge}-z)}\]$${alert type="success"}在什么情况下适合使用 Sigmoid 激活函数呢？Sigmoid 函数的输出范围是 0 到 1。由于输出值限定在 0 到 1，因此它对每个神经元的输出进行了归一化；用于将预测概率作为输出的模型。由于概率的取值范围是 0 到 1，因此 Sigmoid 函数非常合适；梯度平滑，避免「跳跃」的输出值；函数是可微的。这意味着可以找到任意两个点的 sigmoid 曲线的斜率；明确的预测，即非常接近 1 或 0。{/alert}{alert type="warning"}Sigmoid 激活函数有哪些缺点？倾向于梯度消失；函数输出不是以 0 为中心的，这会降低权重更新的效率；Sigmoid 函数执行指数运算，计算机运行得较慢。{/alert}2. Tanh / 双曲正切激活函数tanh 激活函数的图像也是 S 形，表达式如下：$$f(x)=tanh(x)=\frac{2}{1+e^{-2x}}-1$$tanh 是一个双曲正切函数。tanh 函数和 sigmoid 函数的曲线相对相似。但是它比 sigmoid 函数更有一些优势。首先，当输入较大或较小时，输出几乎是平滑的并且梯度较小，这不利于权重更新。二者的区别在于输出间隔，tanh 的输出间隔为 1，并且整个函数以 0 为中心，比 sigmoid 函数更好；在 tanh 图中，负输入将被强映射为负，而零输入被映射为接近零。注意：在一般的二元分类问题中，tanh 函数用于隐藏层，而 sigmoid 函数用于输出层，但这并不是固定的，需要根据特定问题进行调整。3. ReLU 激活函数ReLU 激活函数图像如上图所示，函数表达式如下：{alert type="success"}ReLU 函数是深度学习中较为流行的一种激活函数，相比于 sigmoid 函数和 tanh 函数，它具有如下优点：当输入为正时，不存在梯度饱和问题。计算速度快得多。ReLU 函数中只存在线性关系，因此它的计算速度比 sigmoid 和 tanh 更快{/alert}{alert type="warning"}当然，它也有缺点：Dead ReLU 问题。当输入为负时，ReLU 完全失效，在正向传播过程中，这不是问题。有些区域很敏感，有些则不敏感。但是在反向传播过程中，如果输入负数，则梯度将完全为零，sigmoid 函数和 tanh 函数也具有相同的问题；我们发现 ReLU 函数的输出为 0 或正数，这意味着 ReLU 函数不是以 0 为中心的函数。{/alert}4. Leaky ReLU它是一种专门设计用于解决 Dead ReLU 问题的激活函数：ReLU vs Leaky ReLU为什么 Leaky ReLU 比 ReLU 更好？$$f(y_i)=\begin{cases}y_i,&\text{if }y_i>0\\a_iy_i,&\text{if }y_i\leq0\end{cases}.$$1、Leaky ReLU 通过把 x 的非常小的线性分量给予负输入（0.01x）来调整负值的零梯度（zero gradients）问题；2、leak 有助于扩大 ReLU 函数的范围，通常 a 的值为 0.01 左右；3、Leaky ReLU 的函数范围是（负无穷到正无穷）。注意：从理论上讲，Leaky ReLU 具有 ReLU 的所有优点，而且 Dead ReLU 不会有任何问题，但在实际操作中，尚未完全证明 Leaky ReLU 总是比 ReLU 更好。5. ELUELU vs Leaky ReLU vs ReLUELU 的提出也解决了 ReLU 的问题。与 ReLU 相比，ELU 有负值，这会使激活的平均值接近零。均值激活接近于零可以使学习更快，因为它们使梯度更接近自然梯度。显然，ELU 具有 ReLU 的所有优点，并且：没有 Dead ReLU 问题，输出的平均值接近 0，以 0 为中心；ELU 通过减少偏置偏移的影响，使正常梯度更接近于单位自然梯度，从而使均值向零加速学习；ELU 在较小的输入下会饱和至负值，从而减少前向传播的变异和信息。一个小问题是它的计算强度更高。与 Leaky ReLU 类似，尽管理论上比 ReLU 要好，但目前在实践中没有充分的证据表明 ELU 总是比 ReLU 好。6. PReLU（Parametric ReLU）PReLU 也是 ReLU 的改进版本：$$f(y_i)=\begin{cases}y_i,&\text{if }y_i>0\\a_iy_i,&\text{if }y_i\leq0\end{cases}.$$看一下 PReLU 的公式：参数α通常为 0 到 1 之间的数字，并且通常相对较小。如果 a_i= 0，则 f 变为 ReLU如果 a_i> 0，则 f 变为 leaky ReLU如果 a_i 是可学习的参数，则 f 变为 PReLUPReLU 的优点如下：在负值域，PReLU 的斜率较小，这也可以避免 Dead ReLU 问题。与 ELU 相比，PReLU 在负值域是线性运算。尽管斜率很小，但不会趋于 0。7. SoftmaxSoftmax 是用于多类分类问题的激活函数，在多类分类问题中，超过两个类标签则需要类成员关系。对于长度为 K 的任意实向量，Softmax 可以将其压缩为长度为 K，值在（0，1）范围内，并且向量中元素的总和为 1 的实向量。Softmax 与正常的 max 函数不同：max 函数仅输出最大值，但 Softmax 确保较小的值具有较小的概率，并且不会直接丢弃。我们可以认为它是 argmax 函数的概率版本或「soft」版本。Softmax 函数的分母结合了原始输出值的所有因子，这意味着 Softmax 函数获得的各种概率彼此相关。Softmax 激活函数的主要缺点是：在零点不可微；负输入的梯度为零，这意味着对于该区域的激活，权重不会在反向传播期间更新，因此会产生永不激活的死亡神经元。8. Swish函数表达式：y = x * sigmoid (x)Swish 的设计受到了 LSTM 和高速网络中 gating 的 sigmoid 函数使用的启发。我们使用相同的 gating 值来简化 gating 机制，这称为 self-gating。self-gating 的优点在于它只需要简单的标量输入，而普通的 gating 则需要多个标量输入。这使得诸如 Swish 之类的 self-gated 激活函数能够轻松替换以单个标量为输入的激活函数（例如 ReLU），而无需更改隐藏容量或参数数量。{alert type="success"}Swish 激活函数的主要优点如下：「无界性」有助于防止慢速训练期间，梯度逐渐接近 0 并导致饱和；（同时，有界性也是有优势的，因为有界激活函数可以具有很强的正则化，并且较大的负输入问题也能解决）；导数恒 > 0；平滑度在优化和泛化中起了重要作用。{/alert}9. Maxout在 Maxout 层，激活函数是输入的最大值，因此只有 2 个 maxout 节点的多层感知机就可以拟合任意的凸函数。单个 Maxout 节点可以解释为对一个实值函数进行分段线性近似 (PWL) ，其中函数图上任意两点之间的线段位于图（凸函数）的上方。$ReLU=\max\bigl(0,x\bigr),\mathrm{abs}\bigl(x\bigr)=\max\bigl(x,-x\bigr)$Maxout 也可以对 d 维向量（V）实现：假设两个凸函数 h_1(x) 和 h_2(x)，由两个 Maxout 节点近似化，函数 g(x) 是连续的 PWL 函数。$$g\bigl(x\bigr)=h_1\bigl(x\bigr)-h_2\bigl(x\bigr)$$因此，由两个 Maxout 节点组成的 Maxout 层可以很好地近似任何连续函数。10. SoftplusSoftplus 函数：f（x）= ln（1 + exp x）Softplus 的导数为f ′(x)=exp(x) / ( 1+exp⁡ x )= 1/ (1 +exp(−x ))，也称为 logistic / sigmoid 函数。Softplus 函数类似于 ReLU 函数，但是相对较平滑，像 ReLU 一样是单侧抑制。它的接受范围很广：(0, + inf)。
不同于图像处理，神经网络在处理其他不少领域，常常需要单特征分类，本例实现同半径的圆进行多分类（3分类），特征即为圆的半径。输入层12节点，一个6节点的隐藏层，输出层3个节点。1.目标2.开发环境3.准备数据4.处理数据5.构建BP神经网络总结1.目标通过BP算法实现对不同半径的圆的分类。2.开发环境Python3.10;jupyter notebook3.准备数据目的： 生成3类圆在第一象限内的坐标（圆心都是原点）第1类：半径范围为1~10，分类标识为‘0’第2类：半径范围为10~20，分类标识为‘1’第3类：半径范围为20~30，分类标识为‘2’代码如下：data_generate.pyimport numpy as np import math import random import csv # 只生成第一象限内的坐标即可。每个圆生成12个坐标(x,y)，相当于12个特征维度 def generate_circle(lower, upper): # 圆在第一象限内的坐标 data_ur = np.zeros(shape=(12, 2)) # 在上下限范围内，随机产生一个值作为半径 radius = random.randint(int(lower), int(upper)) # 在0~90度内，每隔7.5度取一次坐标，正好取12次 angles = np.arange(0, 0.5 * np.pi, 1 / 24 * np.pi) for i in range(12): temp_ur = np.zeros(2) x = round(radius * math.cos(angles[i]), 2) y = round(radius * math.sin(angles[i]), 2) temp_ur[0] = x temp_ur[1] = y data_ur[i] = temp_ur return data_ur, label # 将坐标保存到CSV文件中 def save2csv(data, batch, label): out = open("D:\\circles.csv", 'a', newline='') csv_write = csv.writer(out, dialect='excel') length = int(data.size / 2) for i in range(length): string = str(data[i][0]) + ',' + str(data[i][1]) + ',' + str(batch) + ',' + str(label) temp = string.split(',') csv_write.writerow(temp) out.close() if __name__ == "__main__": ''' 生成3类圆，标签（label）分别为：0、1、2 第1类圆的半径下限为1，上限为10 第2类圆的半径下限为10，上限为20 第3类圆的半径下限为20，上限为30 圆心都为原点 ''' lower = [1, 10, 20] # 半径随机值的下限 upper = [10, 20, 30] # 半径随机值的上限 label = ['0', '1', '2'] # 种类的标签 for i in range(len(label)): # 每类数据生成50组 for j in range(50): data, label = generate_circle(lower[i], upper[i]) batch = 50 * i + j + 1 # 数据的批次，用来区分每个坐标是属于哪个圆的 save2csv(data, batch, label[i])共3类圆，每类生成50个圆，每个圆有12个坐标，因此在输出文件D:\circles.csv中总共有3×50×12=1800行数据：通过生成的坐标绘制散点图如下：图中蓝色的点是label为0的圆，绿色的点是label为1的圆，红色的点是label为2的圆。4.处理数据目标： 根据第3步获得的坐标，计算每个圆的半径（勾股定理）作为神经网络的输入。代码如下：data_process.pyimport csv import math def process(file_name): # 要读取的CSV文件 csv_file = csv.reader(open(file_name, encoding='utf-8')) # 要生成的CSV文件 out_file = open("D:\\circles_data.csv", 'a', newline='') csv_write = csv.writer(out_file, dialect='excel') # 将csv_file每一行的圆坐标取出，如果是同一批次的（同一个圆），则写入到out_file的一行中 rows = [row for row in csv_file] current_batch = 'unknown' current_label = 'unknown' data_list = [] for r in rows: # 将无关字符都替换为空格 temp_string = str(r).replace('[', '').replace(']', '').replace('\'', '') # 将字符串以逗号分隔 item = str(temp_string).split(',') # 分别取出x轴坐标、y轴坐标、批次、标签 x = float(item[0]) y = float(item[1]) batch = item[2] label = item[3] # 如果是同一批次（同一个圆），则都放入data_list中 if current_batch == batch: # 根据勾股定理计算半径 distance = math.sqrt(pow(x, 2) + pow(y, 2)) data_list.append(distance) # 如果不是同一批次（同一个圆），则在末尾加上标签后，作为一行写入输出文件 else: if len(data_list) != 0: # 这个地方需注意一下，最后的标签用3列来表示，而不是一列 if label.strip() == '0': data_list.append(1) data_list.append(0) data_list.append(0) elif label.strip() == '1': data_list.append(0) data_list.append(1) data_list.append(0) else: data_list.append(0) data_list.append(0) data_list.append(1) result_string = str(data_list).replace('[', '').replace(']', '').replace('\'', '').strip() csv_write.writerow(result_string.split(',')) # 清空data_list，继续写入下一个批次 data_list.clear() distance = math.sqrt(pow(x, 2) + pow(y, 2)) data_list.append(distance) current_batch = batch current_label = label # 确保最后一个批次的数据能写入 if current_label.strip() == '0': data_list.append(1) data_list.append(0) data_list.append(0) elif current_label.strip() == '1': data_list.append(0) data_list.append(1) data_list.append(0) else: data_list.append(0) data_list.append(0) data_list.append(1) result_string = str(data_list).replace('[', '').replace(']', '').replace('\'', '').strip() csv_write.writerow(result_string.split(',')) # 关闭输出文件 out_file.close() if __name__ == "__main__": process('D:\\circles.csv')需要注意的是，生成的CSV文件共有15列，前12列为坐标对应的半径值，最后三列组合起来表示分类（label）：(1,0,0)表示类型为“0”的圆，(0,1,0)表示类型为“1”的圆，(0,0,1)表示类型为“2”的圆，这样做的目的是为了下一步使用神经网络时处理起来方便。5.构建BP神经网络上一步处理好的数据可以作为训练数据，命名为：circles_data_training.csv重复第3步和第4步，可以生成另一批数据作为测试数据，命名为：circles_data_test.csv当然，也可以手动划分出训练数据和测试数据。训练数据和测试数据在输入时，做了矩阵的转置，将列转置为行。代码如下：data_analysis_bpnn.pyimport pandas as pd import numpy as np import datetime from sklearn.utils import shuffle # 1.初始化参数 def initialize_parameters(n_x, n_h, n_y): np.random.seed(2) # 权重和偏置矩阵 w1 = np.random.randn(n_h, n_x) * 0.01 b1 = np.zeros(shape=(n_h, 1)) w2 = np.random.randn(n_y, n_h) * 0.01 b2 = np.zeros(shape=(n_y, 1)) # 通过字典存储参数 parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2} return parameters # 2.前向传播 def forward_propagation(X, parameters): w1 = parameters['w1'] b1 = parameters['b1'] w2 = parameters['w2'] b2 = parameters['b2'] # 通过前向传播来计算a2 z1 = np.dot(w1, X) + b1 # 这个地方需注意矩阵加法：虽然(w1*X)和b1的维度不同，但可以相加 a1 = np.tanh(z1) # 使用tanh作为第一层的激活函数 z2 = np.dot(w2, a1) + b2 a2 = 1 / (1 + np.exp(-z2)) # 使用sigmoid作为第二层的激活函数 # 通过字典存储参数 cache = {'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2} return a2, cache # 3.计算代价函数 def compute_cost(a2, Y, parameters): m = Y.shape[1] # Y的列数即为总的样本数 # 采用交叉熵（cross-entropy）作为代价函数 logprobs = np.multiply(np.log(a2), Y) + np.multiply((1 - Y), np.log(1 - a2)) cost = - np.sum(logprobs) / m return cost # 4.反向传播（计算代价函数的导数） def backward_propagation(parameters, cache, X, Y): m = Y.shape[1] w2 = parameters['w2'] a1 = cache['a1'] a2 = cache['a2'] # 反向传播，计算dw1、db1、dw2、db2 dz2 = a2 - Y dw2 = (1 / m) * np.dot(dz2, a1.T) db2 = (1 / m) * np.sum(dz2, axis=1, keepdims=True) dz1 = np.multiply(np.dot(w2.T, dz2), 1 - np.power(a1, 2)) dw1 = (1 / m) * np.dot(dz1, X.T) db1 = (1 / m) * np.sum(dz1, axis=1, keepdims=True) grads = {'dw1': dw1, 'db1': db1, 'dw2': dw2, 'db2': db2} return grads # 5.更新参数 def update_parameters(parameters, grads, learning_rate=0.0075): w1 = parameters['w1'] b1 = parameters['b1'] w2 = parameters['w2'] b2 = parameters['b2'] dw1 = grads['dw1'] db1 = grads['db1'] dw2 = grads['dw2'] db2 = grads['db2'] # 更新参数 w1 = w1 - dw1 * learning_rate b1 = b1 - db1 * learning_rate w2 = w2 - dw2 * learning_rate b2 = b2 - db2 * learning_rate parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2} return parameters # 建立神经网络 def nn_model(X, Y, n_h, n_input, n_output, num_iterations=10000, print_cost=False): np.random.seed(3) n_x = n_input # 输入层节点数 n_y = n_output # 输出层节点数 # 1.初始化参数 parameters = initialize_parameters(n_x, n_h, n_y) # 梯度下降循环 for i in range(0, num_iterations): # 2.前向传播 a2, cache = forward_propagation(X, parameters) # 3.计算代价函数 cost = compute_cost(a2, Y, parameters) # 4.反向传播 grads = backward_propagation(parameters, cache, X, Y) # 5.更新参数 parameters = update_parameters(parameters, grads) # 每1000次迭代，输出一次代价函数 if print_cost and i % 1000 == 0: print('迭代第%i次，代价函数为：%f' % (i, cost)) return parameters # 对模型进行测试 def predict(parameters, x_test, y_test): w1 = parameters['w1'] b1 = parameters['b1'] w2 = parameters['w2'] b2 = parameters['b2'] z1 = np.dot(w1, x_test) + b1 a1 = np.tanh(z1) z2 = np.dot(w2, a1) + b2 a2 = 1 / (1 + np.exp(-z2)) # 结果的维度 n_rows = y_test.shape[0] n_cols = y_test.shape[1] # 预测值结果存储 output = np.empty(shape=(n_rows, n_cols), dtype=int) # 取出每条测试数据的预测结果 for i in range(n_cols): # 将每条测试数据的预测结果（概率）存为一个行向量 temp = np.zeros(shape=n_rows) for j in range(n_rows): temp[j] = a2[j][i] # 将每条结果（概率）从小到大排序，并获得相应下标 sorted_dist = np.argsort(temp) length = len(sorted_dist) # 将概率最大的置为1，其它置为0 for k in range(length): if k == sorted_dist[length - 1]: output[k][i] = 1 else: output[k][i] = 0 print('预测结果：') print(output) print('真实结果：') print(y_test) count = 0 for k in range(0, n_cols): if output[0][k] == y_test[0][k] and output[1][k] == y_test[1][k] and output[2][k] == y_test[2][k]: count = count + 1 acc = count / int(y_test.shape[1]) * 100 print('准确率：%.2f%%' % acc) if __name__ == "__main__": # 读取数据 data_set = pd.read_csv('D:\\circles_data_training.csv', header=None) data_set = shuffle(data_set) # 打乱数据的输入顺序 # 取出“特征”和“标签”，并做了转置，将列转置为行 X = data_set.ix[:, 0:11].values.T # 前12列是特征 Y = data_set.ix[:, 12:14].values.T # 后3列是标签 Y = Y.astype('uint8') # 开始训练 start_time = datetime.datetime.now() # 输入12个节点，隐层6个节点，输出3个节点，迭代10000次 parameters = nn_model(X, Y, n_h=6, n_input=12, n_output=3, num_iterations=10000, print_cost=True) end_time = datetime.datetime.now() print("用时：" + str((end_time - start_time).seconds) + 's' + str(round((end_time - start_time).microseconds / 1000)) + 'ms') # 对模型进行测试 data_test = pd.read_csv('D:\\circles_data_test.csv', header=None) x_test = data_test.ix[:, 0:11].values.T y_test = data_test.ix[:, 12:14].values.T y_test = y_test.astype('uint8') predict(parameters, x_test, y_test)码中需要注意的几个关键参数：learning_rate=0.0075，学习率（可调）n_h=6，隐藏层节点数（可调）n_input=12，输入层节点数n_output=3，输出层节点数num_iterations=10000，迭代次数（可调）另外，对于predict(parameters, x_test, y_test)函数需要说明一下：a2矩阵是最终的预测结果，但是是以概率的形式表示的（可以打印看一下）。通过比较3个类的概率，选出概率最大的那个置为1，其它两个置为0，形成output矩阵。运行结果：上图中第一红框表示神经网络预测出的分类结果，第二个红框表示测试集中真实的分类（(1,0,0)表示这个圆属于类型“0”）。每次运行时，正确率可能不一样，最高能达到100%。通过调整刚才提到的关键参数中的学习率、隐藏层节点数、迭代次数可以提高正确率。总结 神经网络的输入为12个半径值，输出结果为一个3维向量，其中置1的位就是对应的分类。 在实际应用中，12个半径值对应12个特征，3维向量表示能分3类。只要根据实际应用的需要修改特征数和分类数即可将上述程序应用于不同分类场景。
神经网络简述前向传播反向传播四、深层神经网络（Deep Neural Network）1.单个神经元2.由神经元组成的神经网络3.目标函数4.求解损失函数对某个权值的梯度5.反向传播算法Backpropgation5.2 计算一个梯度5.3 反向传播误差6.优化示例神经网络简述神经网络，就是在Logistic regression的基础上增加了一个或几个隐层（hidden layer），下面展示的是一个最最最简单的神经网络，只有两层： 两层神经网络 需要注意的是，上面的图是“两层”，而不是三层或者四层，输入和输出不算层!这里，我们先规定一下记号（Notation）：z是x和w、b线性运算的结果，z=wx+b；a是z的激活值；下标的1,2,3,4代表该层的第i个神经元（unit）；上标的[1],[2]等代表当前是第几层。y^代表模型的输出，y才是真实值，也就是标签另外，有一点经常搞混：上图中的x1，x2，x3，x4不是代表4个样本！而是一个样本的四个特征（4个维度的值）！你如果有m个样本，代表要把上图的过程重复m次： 神经网络的“两个传播”： 前向传播（Forward Propagation）前向传播就是从input，经过一层层的layer，不断计算每一层的z和a，最后得到输出y^ 的过程，计算出了y^，就可以根据它和真实值y的差别来计算损失（loss）。反向传播（Backward Propagation）反向传播就是根据损失函数L(y^,y)来反方向地计算每一层的z、a、w、b的偏导数（梯度），从而更新参数。前向传播和反向传播： 每经过一次前向传播和反向传播之后，参数就更新一次，然后用新的参数再次循环上面的过程。这就是神经网络训练的整个过程。前向传播如果用for循环一个样本一个样本的计算，显然太慢，是使用Vectorization，把m个样本压缩成一个向量X来计算，同样的把z、a都进行向量化处理得到Z、A，这样就可以对m的样本同时进行表示和计算了。不熟悉的朋友可以看这里：这样，我们用公式在表示一下我们的两层神经网络的前向传播过程：Layer 1:Z[1] = W[1]·X + b[1]A[1] = σ(Z[1])Layer 2:Z[2] = W[2]·A[1] + b[2]A[2] = σ(Z[2])而我们知道，X其实就是A[0]，所以不难看出:每一层的计算都是一样的：Layer i:Z[i] = W[i]·A[i-1] + b[i]A[i] = σ(Z[i])（注：σ是sigmoid函数）因此，其实不管我们神经网络有几层，都是将上面过程的重复。对于 损失函数，就跟Logistic regression中的一样，使用 “交叉熵（cross-entropy）”，公式就是二分类问题：L(y^,y) = -[y·log(y^ )+(1-y)·log(1-y^ )]- 多分类问题：L=-Σy(j)·y^(j)这个是每个样本的loss，我们一般还要计算整个样本集的loss，也称为cost，用J表示，J就是L的平均：J(W,b) = 1/m·ΣL(y^(i),y(i))上面的求Z、A、L、J的过程就是正向传播。反向传播反向传播说白了根据根据J的公式对W和b求偏导，也就是求梯度。因为我们需要用梯度下降法来对参数进行更新，而更新就需要梯度。但是，根据求偏导的链式法则我们知道，第l层的参数的梯度，需要通过l+1层的梯度来求得，因此我们求导的过程是“反向”的，这也就是为什么叫“反向传播”。具体求导的过程，这里就不赘述了。像各种 深度学习框架TensorFlow、Keras，它们都是 只需要我们自己构建正向传播过程， 反向传播的过程是自动完成的，所以大家也确实不用操这个心。进行了反向传播之后，我们就可以根据每一层的参数的梯度来更新参数了，更新了之后，重复正向、反向传播的过程，就可以不断训练学习更好的参数了。四、深层神经网络（Deep Neural Network）前面的讲解都是拿一个两层的很浅的神经网络为例的。深层神经网络也没什么神秘，就是多了几个/几十个/上百个hidden layers罢了。可以用一个简单的示意图表示：深层神经网络:注意，在深层神经网络中，我们在中间层使用了 “ReLU”激活函数，而不是sigmoid函数了，只有在最后的输出层才使用了sigmoid函数，这是因为 ReLU函数在求梯度的时候更快，还可以一定程度上防止梯度消失现象，因此在深层的网络中常常采用。关于深层神经网络，我们有必要再详细的观察一下它的结构，尤其是 每一层的各个变量的维度，毕竟我们在搭建模型的时候，维度至关重要。我们设:总共有m个样本，问题为二分类问题（即y为0,1）；网络总共有L层，当前层为l层（l=1,2,...,L）；第l层的单元数为n[l]；那么下面参数或变量的维度为：W[l]:(n[l],n[l-1])（该层的单元数，上层的单元数）b[l]:(n[l],1)z[l]:(n[l],1)Z[l]:(n[l],m)a[l]:(n[l],1)A[l]:(n[l],m)X:(n[0],m)Y:(1,m)可能有人问，为什么 W和b的维度里面没有m？因为 W和b对每个样本都是一样的，所有样本采用同一套参数（W，b），而Z和A就不一样了，虽然计算时的参数一样，但是样本不一样的话，计算结果也不一样，所以维度中有m。深度神经网络的正向传播、反向传播和前面写的2层的神经网络类似，就是多了几层，然后中间的激活函数由sigmoid变为ReLU了。1.单个神经元神经网络是由一系列神经元组成的模型，每一个神经元实际上做得事情就是实现非线性变换。如下图就是一个神经元的结构：神经元将两个部分：上一层的输出(x1,x2,....,xn)与权重(w1,w2,....,wn),对应相乘相加，然后再加上一个偏置 b之后的值经过激活函数处理后完成非线性变换。记 z = w ⋅ x + b ,a=σ(z)，则 z 是神经元非线性变换之前的结果，这部分仅仅是一个简单的线性函数。σ 是Sigmod激活函数，该函数可以将无穷区间内的数映射到(-1,1)的范围内。a 是神经元将 z z 进行非线性变换之后的结果。Sigmod函数图像如下图$$\mathrm{sigmod(x)=\frac{1}{1+e^{-x}}}$$因此，结果 a 就等于：这里再强调一遍，神经元的本质就是做非线性变换2.由神经元组成的神经网络神经元可以理解成一个函数，神经网络就是由很多个非线性变换的神经元组成的模型，因此神经网络可以理解成是一个非常复杂的复合函数。对于上图中的网络：(x1,x2,....,xn)为n维输入向量Wij,表示后一层低i个神经元与前一层第j个神经元之间的权值z = Wx + b是没有经过激活函数非线性变换之前的结果a=σ(z)，是 z 经过激活函数非线性变换之后的结果\mathrm{s=U^Ta}，为网络最终的输出结果。3.目标函数以折页损失为目标函数：$$\mathrm{minmizeJ}=\max(\Delta+s_{c}-s,0)$$其中$\mathrm{s_c=U^T\sigma(Wx_c+b),s=U^T\sigma(Wx+b)}$。一般可以把$\Delta$固定下来,比如设为1或者10。一般来说，学习算法的学习过程就是优化调整参数，使得损失函数，或者说预测结果和实际结果的误差减小。BP算法其实是一个双向算法，包含两个步骤：1.正向传递输入信息，得到 Loss 值。2.反向传播误差，从而由优化算法来调整网络权值。4.求解损失函数对某个权值的梯度其中$\mathrm{s_c=U^T\sigma(Wx_c+b),s=U^T\sigma(Wx+b)}$。一般可以把$\Delta$固定下来,比如设为1或者10。对于上面的图, 假设图中指示出的网络中的某个权值 w$_\mathrm{jk}^\mathrm{l}$ 发生了一个小的改变 $\Delta w_\mathrm{jk}^\mathrm{l}$ , 假设网络最终损失函数的输出为$\mathbb{C}$,则 C 应该是关于 $\mathrm{w}_\mathrm{jk}^{\mathrm{l}}$ 的一个复合函数。 所谓复合函数,就是把 $\mathbb{C}$ 看成因变量,则$\mathrm{w}_\mathrm{jk}^{\mathrm{l}}$ 可以看成导致 C 改变的自变量, 比如假设有一个复 合函数 $\mathrm{y=f(g(x))}$,则 y 就好比这里的$\mathbb{C}$ , x 就好比$\mathrm{w}_{\mathrm{jk}}^{\mathrm{l}}$, w$_{jk}^{\mathrm{l}}$ 每经过一层网络可以看成是经过某个函数的处理。而下面求写的时候都用偏导数, 是因为虽然我们这里只关注了一个 w$_\mathrm{jk}^{\mathrm{l}}$,但是实际上网络中的每一个 w 都可以看成一个 x。显然,这个$\Delta w_\mathrm{jk}^\mathrm{l}$的变化会引起下一层直接与其相连的一个神经元,以及下一层之后所有神经元直到 最终输出 C 的变化, 如图中蓝线标记的就是该权值变化的影响传播路径。 把 C 的改变记为 $\Delta\mathbb{C}$, 则根据高等数学中导数的知识可以得到:则神经元 a$_\mathrm{j}^1$ 下面一层第 q 个与其相连的神经元 a$_{\mathrm{q}}^{1+1}$ 的变化为:$$ \Delta\mathrm{a_q^{1+1}}\approx\frac{\partial\mathrm{a_q^{1+1}}}{\partial\mathrm{a_j^1}}\Delta\mathrm{a_j^1} $$将 $(2)$ 代入 (3) 可以得到:$$ \Delta\mathrm{a_{q}^{l+1}}\:\approx\:\frac{\partial\mathrm{a_{q}^{l+1}}}{\partial\mathrm{a_{j}^{l}}}\:\frac{\partial\mathrm{a_{j}^{l}}}{\partial\mathrm{w_{jk}^{l}}}\:\Delta\mathrm{w_{jk}^{l}} $$假设从 $\mathrm{a_j}^1$ 到 C的一条路径为$\mathrm{a_j}^1,\mathrm{a_q}^{1+1},...,\mathrm{a_n}^{\mathrm{L-1}},\mathrm{a_m}^{\mathrm{L}}$,则在该条路径上 C 的变化量 $\Delta C$ 为:$$\Delta\mathrm{C}\approx\frac{\partial\mathrm{C}}{\partial\mathrm{a}_\mathrm{m}^\mathrm{L}}\frac{\partial\mathrm{a}_\mathrm{m}^\mathrm{L}}{\partial\mathrm{a}_\mathrm{n}^\mathrm{L-1}}\frac{\partial\mathrm{a}_\mathrm{n}^\mathrm{L-1}}{\partial\mathrm{a}_\mathrm{p}^\mathrm{L-2}}[USD3P]\frac{\partial\mathrm{a}_\mathrm{q}^\mathrm{l+1}}{\partial\mathrm{a}_\mathrm{j}^\mathrm{l}}\frac{\partial\mathrm{a}_\mathrm{j}^\mathrm{l}}{\partial\mathrm{w}_\mathrm{jk}^\mathrm{l}}\Delta\mathrm{w}_\mathrm{jk}^\mathrm{l}$$至此,我们已经得到了一条路径上的变化量, 其实本质就是链式求导法则,或者说是复合函数求导法则。那么整个的变化量就县把所有可能链路上的变化量加起来:$$\Delta\mathrm{C}\approx\sum_{\mathrm{minp[USD3P]q}}\frac{\partial\mathrm{C}}{\partial\mathrm{a_{m}^{L}}}\frac{\partial\mathrm{a_{m}^{L}}}{\partial\mathrm{a_{n}^{L-1}}}\frac{\partial\mathrm{a_{n}^{L-1}}}{\partial\mathrm{a_{p}^{L-2}}}[USD3P]\frac{\partial\mathrm{a_{q}^{l+1}}}{\partial\mathrm{a_{j}^{l}}}\frac{\partial\mathrm{a_{j}^{l}}}{\partial\mathrm{w_{jk}^{l}}}\Delta\mathrm{w_{jk}^{l}}$$$$\begin{aligned}\text{则 C 对某个权值 w}_{\mathrm{jk}}^{\dagger}&\text{的梯度为:}\\&\frac{\partial\mathrm{C}}{\partial\mathrm{w}_{\mathrm{jk}}^{\dagger}}\approx\sum_{\mathrm{nmp},<0}\frac{\partial\mathrm{C}}{\partial\mathrm{a}_{\mathrm{m}}^{\mathrm{L}}}\frac{\partial\mathrm{a}_{\mathrm{m}}^{\mathrm{L}}}{\partial\mathrm{a}_{\mathrm{n}}^{\mathrm{L}-1}}\frac{\partial\mathrm{a}_{\mathrm{n}}^{\mathrm{L}-1}}{\partial\mathrm{a}_{\mathrm{p}}^{\mathrm{L}-2}}[USD3P]\frac{\partial\mathrm{a}_{\mathrm{q}}^{\mathrm{l}+1}}{\partial\mathrm{a}_{\mathrm{j}}^{\mathrm{l}}}\frac{\partial\mathrm{a}_{\mathrm{j}}^{\mathrm{l}}}{\partial\mathrm{w}_{\mathrm{jk}}^{\mathrm{l}}}\end{aligned}$$到这里从数学分析的角度来说，我们可以知道这个梯度是可以计算和求解的。总结：1.每两个神经元之间是由一条边连接的，这个边就是一个权重值，它是后一个神经元 z 部分，也就是未经激活函数非线性变换之前的结果对前一个神经元的 a 部分，也就是经激活函数非线性变换之后的结果的偏导数。2.一条链路上所有偏导数的乘积就是这条路径的变化量。3.所有路径变化量之和就是整个损失函数的变化量。5.反向传播算法Backpropgation5.1 明确一些定义对于上面的神经网络, 首先明确下面一些定义:5.2 计算一个梯度假设损失函数为：J=(1+sc −s)来计第一下 $\frac{\partial\mathrm{J}}{\partial\mathrm{W}_{14}^{(1)}}$,由于 s 是网络的输出, $\frac{\partial\mathrm{J}}{\partial\mathrm{s}}=-1$, 所以只需要计算$\frac{\partial\mathrm{s}}{\partial\mathrm{W}_{14}^{(1)}}$ 即可。 而由于 s 激活函数就是1, 所以有:$$ \mathrm{s=a_1^{(3)}=z_1^{(3)}=W_{11}^{(2)}a_1^{(2)}+W_{12}^{(2)}a_2^{(2)}} $$$$ \frac{\partial\mathrm{s}}{\partial\mathrm{W}_{14}^{(1)}}=\frac{\partial(\mathrm{W}_{11}^{(2)}\mathrm{a}_{1}^{(2)}+\mathrm{W}_{12}^{(2)}\mathrm{a}_{2}^{(2)})}{\partial\mathrm{W}_{14}^{(1)}} $$所以：$$\frac{\partial s}{\partial\mathrm{W}_{14}^{(1)}}=\frac{\partial\mathrm{W}_{11}^{(2)}\mathrm{a}_{1}^{(2)}}{\partial\mathrm{W}_{14}^{(1)}}=\mathrm{W}_{11}^{(2)}\frac{\partial\mathrm{a}_{1}^{(2)}}{\partial\mathrm{W}_{14}^{(1)}}$$由于代入公式(9)可以得到$$\begin{gathered}\frac{\partial\mathrm{s}}{\partial\mathrm{W}_{14}^{(1)}}=\mathrm{W}_{11}^{(2)}\frac{\partial\mathrm{a}_{1}^{(2)}}{\partial\mathrm{W}_{14}^{(1)}}=\mathrm{W}_{11}^{(2)}\frac{\partial\mathrm{a}_{1}^{(2)}}{\partial\mathrm{z}_{1}^{(2)}}\frac{\partial\mathrm{z}_{1}^{(2)}}{\partial\mathrm{W}_{14}^{(1)}1} \=\operatorname{W}_{11}^{(2)}\mathfrak{\sigma}^{\prime}(\mathfrak{z}_{1}^{(2)})\frac{\partial(\mathfrak{b}_{1}^{(1)}+\sum_{\mathrm{k}}\mathfrak{a}_{\mathrm{k}}^{(1)}\mathfrak{W}_{1\text{k}} ^ { ( 1 ) })}{\partial\mathrm{W}_{14}^{(1)}} \end{gathered}$$$$ \text{公式}(10)中\frac{\partial(\mathrm{h}_{1}^{(1)}+\sum_{k}a_{k}^{(1)}W_{1k}^{(1)})}{\partial\mathrm{W}_{14}^{(1)}}=\mathrm{a}_{4}^{(1)}。 $$此外还有一个更重要的变换, 同样的方法可以求出:$$ \frac{\partial\mathrm{J}}{\partial\mathrm{z}_1^{(2)}}=\mathrm{W}_{11}^{(2)}\mathrm{\sigma}^{\prime}(\mathrm{z}_1^{(2)}) $$$$ \frac{\partial\mathrm{s}}{\partial\mathrm{W}_{14}^{(1)}}=\frac{\partial\mathrm{J}}{\partial\mathrm{z}_1^{(2)}}\mathrm{a}_4^{(1)}=\delta_1^{(2)}\mathrm{a}_4^{(1)} $$并且结合前面对 $\delta_\mathrm{j}^{(\mathrm{k})}$ 的定义,公式 (10) 可以写成:所以损失函数对任离一个网络权伯的梯度可以弓成两个值相乘的形式。对于$\mathrm{a}_4^{(1)}$,它是网络前向传递过程中的一个神经元的输出, 我们当然可以在网络前向传递的时候将它保存下来。而对于 $\delta_1^{(2)}$,它是反向传播过来的梯度,也就是 J 对 z 的梯度, 下面来者如何通过后面神经元的 $\delta_{\mathrm{i}}^{(\mathrm{k})}$ 反向传播得到 前一个神经元处的 $\delta_\mathrm{j}^{(k-1)}$ 。5.3 反向传播误差到这里需要注意，反向传播仅指用于计算梯度的方法，它并不是用于神经网络的整个学习算法。通过反向传播算法算法得到梯度之后，可以使用比如随机梯度下降算法等来进行学习。反向传播算法传播的是误差，传播方向是从最后一层依次往前，这是一个迭代的过程。在上面的过程中，我们求得了损失函数对于某个权值的梯度，通过该处的梯度值，可以将其向前传播，得到前一个结点的梯度。$(k-1)$例如对于上面的图, 假设已经求出 z$^{(\mathrm{k})}$ 处的柠伊卡$^{(\mathrm{k})}$,则行误差仅照某杀连接路径, 传递到 $\mathrm{a_j}^{[\mathrm{l}]}$ per处,则该处的梯度为$\delta_{\mathrm{i}}^{(\mathrm{k})}W_{\mathrm{ij}}^{(\mathrm{k}-1)}$。实际上这只是一条路径, $\mathrm{a_j}^{(\mathrm{k-1})}$处可能会收到很多个不同的误差, 例如下面该神经元后面有两条权值 边的情况:这个时候只要把它们相加就好了,所以 a$_{\mathrm{j}}^{(\mathrm{k}-1)}$处,则该处的梯度为$\sum_{\mathrm{i}}\delta_{\mathrm{i}}^{(\mathrm{k})}\mathrm{W}_{\mathrm{ij}}^{(\mathrm{k}-1)}$。6.优化示例再次说一下，反向传播仅指用于计算梯度的方法，它并不是用于神经网络的整个学习算法。通过反向传播算法算法得到梯度之后，还需要使用比如随机梯度下降算法等来进行学习。在上面的过程中, 我们通过反向传播弹法求解出了任意一个$z_j^{(\mathrm{i})}$处的梯度$\delta_j^{(\mathrm{i})}$,为什么是$z_j^{(\mathrm{i})}$而不是$a_j^{(\mathrm{i})}$,因为$z_j^{(\mathrm{i})}$前面就是连接它的w,以随机梯度下降算法为例，w的优化方法为：$$\mathrm{w_{ijnew}^{(1)}=w_{ijold}^{(1)}-\eta\cdot\frac{\partial J}{\partial w_{ij}^{(1)}}=w_{ijold}^{(1)}-\eta\cdot\delta_{i}^{(1+1)}\cdot a_{j}^{(1)}}$$