刘航宇 发布的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
12,710 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
7,156 阅读
3
【高数】形心计算公式讲解大全
6,642 阅读
4
【1】基于STM32CubeMX-STM32GPIO端口开发
5,152 阅读
5
如何判断运放是工作在线性区还是非线性区
4,995 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
软件算法
小实验
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇
嵌入式系统&数字IC爱好者博客
累计撰写
302
篇文章
累计收到
527
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
用户登录
登录
刘航宇(共302篇)
找到
302
篇与
刘航宇
相关的结果
2023-12-26
AI: 机器学习必须懂的几个术语:Label、Feature、Model...
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所选择的,具体采用哪种算法也没有一定要求,根据实际具体问题具体分析。
2023年12月26日
338 阅读
0 评论
2 点赞
面向对象在编程中的概念
前言在刚接触java、C++、Python语言的时候,就知道这是一门面向对象的语言。学不好java的原因找到了,面向对象的语言,没有对象怎么学 ::(狗头) 那么究竟什么是面向对象呢?面向对象,Object Oriented Programming,简称为OOP。说到面向对象,不得不提一嘴面向过程。面向过程面向过程是一种自顶而下的编程模式。把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。举个生活中的例子,假如你想吃红烧肉,你需要买肉,买调料,洗肉,切肉,烧肉,装盘。需要我们具体每一步去实现,每个步骤相互协调,最终盛出来的才是正宗好吃的红烧肉。面向对象面向对象就是将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。还是你想吃红烧肉这个例子,不过这次不同的是你发现了一家餐馆里有红烧肉这道菜,你要做的只是去点菜,就可以吃到红烧肉。针不戳,针不戳,面向对象针不戳,你不用再去关心红烧肉繁琐的制作流程,就能吃到美味的红烧肉。我们接着往下看面向对象的三大特性封装封装就是把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的类进行信息的隐藏。简单的说就是:封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,不需要知道具体是怎么实现的。封装可以有助于提高类和系统的安全性这里有点像FPGA的IP了,电子人懂!继承当多个类中存在相同属性和行为时,将这些内容就可以抽取到一个单独的类中,使得多个类无需再定义这些属性和行为,只需继承那个类即可。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”多态多态同一个行为具有多个不同表现形式或形态的能力。是指一个类实例(对象)的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。菜鸟教程中的例子就很形象面向对象的五大基本原则单一职责原则(Single-Responsibility Principle)简而言之,就是一个类最好只有一个能引起变化的原因,只做一件事,单一职责原则可以看做是低耦合高内聚思想的延伸,提高高内聚来减少引起变化的原因。开放封闭原则(Open-Closed principle)简而言之,就是软件实体应该是可扩展的,但是不可修改。因为修改程序有可能会对原来的程序造成错误。即对扩展开放,对修改封闭里氏替换原则(Liskov-Substitution Principle)简而言之,就是子类一定可以替换父类,子类包含其基类(父类)的功能依赖倒置原则(Dependecy-Inversion Principle)高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。依赖于抽象即对接口编程,不要对实现编程接口隔离原则(Interface-Segregation Principle)简而言之,就是使用多个小的专门的接口,而不要使用一个大的总接口。例如将一个房子再分割成卧室,厨房,厕所等等,而不是将所有功能放在一起面向对象编程的用途使用封装(信息隐藏)可以对外部隐藏数据使用继承可以重用代码使用多态可以重载操作符/方法/函数,即相同的函数名或操作符名称可用于多种任务数据抽象可以用抽象实现项目易于迁移(可以从小项目转换成大项目)同一项目分工软件复杂性可控面向对象编程的应用领域人工智能与专家系统企业级应用神经网络与并行编程办公自动化系统
2023年12月25日
159 阅读
0 评论
3 点赞
2023-12-20
【嵌软】STM32的4种开发方式介绍
与FPGA一样,STM32也属于板级开发,可适用于大多数计算场合,但神经网络这种需要大量并行计算的需求难以满足。STM32的开发主要指的是通过程序实现功能,ST官方提供的开发方式来说从远及近分别是:1、直接读写寄存器2、标准外设驱动库 SPL3、硬件抽象层库 HAL库4、底层库 LL库四种开发方式各有优缺点,可以参考ST官方的测试与说明:标准外设库:这是ST官方提供的一套固件库,包含了STM32所有外设的驱动函数,可以方便地调用。它的优点是稳定、兼容、易用,缺点是占用资源较多,效率较低,更新较慢。寄存器操作:这是直接对STM32的寄存器进行读写的方式,可以实现最底层的控制。它的优点是占用资源最少,效率最高,缺点是难度较大,需要熟悉寄存器的功能和位定义,不利于移植。HAL库:这是ST官方推出的一套新的固件库,基于硬件抽象层(Hardware Abstraction Layer)的思想,提供了更加简洁和统一的接口。它的优点是支持多种IDE,更新较快,易于移植,缺点是文档较少,兼容性较差,有些BUG。CubeMX:这是ST官方提供的一套图形化的配置工具,可以自动生成HAL库的代码,还可以集成一些中间件和应用层的功能。它的优点是操作简单,功能强大,缺点是生成的代码较冗余,不易修改,有些功能不完善。直接读写寄存器 开发是最慢的,可移植性最差,基本不推荐使用,只有个别对时间或是内存要求特别高、或者在写操作系统调度器时才需要直接读写寄存器;标准外设驱动库 是ST最开始提供的库(国内的教程也很多是依据题库出的),现在已经被ST放弃了;HAL库 和 LL库 是近几年推出的库,结合 STM32CubeIDE 使用非常方便, HAL库 性能较差、在STM32系列芯片中可移植性好, LL库 性能好、可移植性差。
2023年12月20日
622 阅读
0 评论
3 点赞
2023-12-11
华为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的个数,之间以空格隔开。示例示例2需要注意的细节类似于【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; }
2023年12月11日
178 阅读
0 评论
0 点赞
2023-12-11
大疆题解:跨时钟域脉冲信号处理—脉冲同步器(快到慢)
问题描述sig_a 是 clka(300M)时钟域的一个单时钟脉冲信号(高电平持续一个时钟clka周期),请设计脉冲同步电路,将sig_a信号同步到时钟域 clkb(100M)中,产生sig_b单时钟脉冲信号(高电平持续一个时钟clkb周期)输出。请用 Verilog 代码描述。clka时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题。电路的接口如下图所示:题解1.1 电路波形图如上图所述,aclk快时钟域发送的信号signal_a,慢时钟域的时钟bclk根本就采集不到,此时不能使用打两拍的方式,要想办法转换思路, 如果能够让同步于快时钟域aclk下的脉冲信号signal_a变长到可以让慢时钟域bclk检测到,那么这个问题就可以完美解决了。 所以先将快时钟域clka下的脉冲信号signal_a,在快时钟域clka的作用下,变为沿信号,产生一个名为adata的中间变量来作为脉冲信号signal_a的沿信号。如上图所示,每当快时钟域aclk检测到signal_a脉冲信号为高时,让adata信号取反,使得signal_a的第一个脉冲变为adata信号的上升沿,signal_a的第二个脉冲变为adata信号的下降沿,后面如果Signal_a信号还有脉冲依然是变为adata信号的上升沿和下降沿。巧妙的利用将“脉冲信号”转化为“沿信号”的思想就可以使慢时钟域的时钟bclk检测到同步于快时钟域aclk且将脉冲信号signal_a转化为沿信号adata, 相当于是把同步于快时钟域aclk的脉冲信号signal_a进行了展宽处理,这样我们就把快时钟域aclk的脉冲信号signal_a通过adata信号“沿”的形式在慢时钟域bclk中得到了保留。接着,我们再对adata信号做打两拍的处理就可以将adata信号同步到慢时钟域clkb中了。bdata0信号是adata信号在慢时钟域bclk下打的第一拍,bdata1信号是adata信号在慢速时钟域bclk下打第二拍,bdata1就是同步于慢速时钟域bclk的稳定信号。最后,采用 边沿检测 的方法,将变为bdata1信号的“沿”再转化为脉冲信号,这里我们使用的方法是采用异或门。需要注意的是不能直接使用bdata0和bdata1来产生沿标志信号,因为bdata0信号的不稳定性可能会导致产生的沿信号也不稳定,所以需要将bdata1信号再打一拍,产生signal_b信号。1.2 代码//快时钟数据同步 module pulse_detect( input clka , input clkb , input rst_n , input sig_a , output sig_b ); wire sig_a; reg adata; reg bdata0; reg bdata1; reg bdata2; always @(posedge clka or negedge rst_n) begin if(~rst_n) begin adata <= 1'd0; end else begin adata <= adata ^ sig_a; end end always @(posedge clkb or negedge rst_n) begin if(~rst_n) begin bdata0 <= 1'd0; bdata1 <= 1'd0; bdata2 <= 1'd0; end else begin bdata0 <= adata; bdata1 <= bdata0; bdata2 <= bdata1; end end assign sig_b = bdata1 ^ bdata2; endmodule注意signal_a是两个脉冲,但是使用“脉冲同步”同步到bclk时钟域确只有一个脉冲了,在使用“脉冲同步”时应注意这一点。所以,脉冲同步一般适用于单比特信号从快时钟域传递慢时钟域的场景。
2023年12月11日
284 阅读
0 评论
2 点赞
2023-11-20
超声模块HC_SR04基本原理与FPGA、STM32应用
HC-SR04硬件概述HC-SR04超声波距离传感器的核心是两个超声波传感器。一个用作发射器,将电信号转换为40 KHz超声波脉冲。接收器监听发射的脉冲。如果接收到它们,它将产生一个输出脉冲,其宽度可用于确定脉冲传播的距离。就是如此简单!该传感器体积小,易于在任何机器人项目中使用,并提供2厘米至600厘米(约1英寸至13英尺)之间出色的非接触范围检测,精度为3mm。接口定义:模式选择:测量操作:一:GPIO模式 外部MCU给模块Trig脚一个大于10uS的高电平脉冲;模块会给出一个与距离等比的高电平脉冲信号,可根据脉宽时间“T”算出: 距离=T*C/2 (C为声速)声速温度公式:c=(331.45+0.61t/℃)m•s-1 (其中330.45是在0℃) 0℃声速: 330.45M/S 20℃声速: 342.62M/S 40℃声速: 354.85M/S0℃-40℃声速误差7%左右。实际应用,如果需要精确距离值,必需要考虑温度影响,做温度补偿。二:UART模式UART 模式波特率设置: 9600 N 1 连接串口。外部MCU或PC发命令0XA0,模块完成测距后发3个返回距离数据,BYTE_H,BYTE_M与BYTE_L。距离计算方式如下(单位mm):距离=((BYTE_H<<16)+(BYTE_M<<8)+ BYTE_L)/1000三:IIC模式IIC地址: 0X57IIC传输格式:写数据:读数据:命令格式:向模块写入0X01,模块开始测距;等待200mS(模块最大测距时间)以上。直接读出3个距离数据。BYTE_H,BYTE_M与BYTE_L。距离计算方式如下(单位mm):距离=((BYTE_H<<16)+(BYTE_M<<8)+ BYTE_L)/1000FPGA实现超声测距本次测距教程一律按基本原理实现,至于UART、ICC测距原理可以网上查询FPGA 产生周期性的 TRIG 脉冲信号,使得超声波模块周期性发出测距脉冲,当这些脉冲发出后遇到障碍物返回,超声波模块将返回的脉冲处理整形后返回给 FPGA,即 ECHO 信号。我们通过对 ECHO 信号的高脉冲保持时间就可以推算出超声波脉冲和障碍物之间的距离。本实例的功能如图三所示,FPGA 产生 10us 脉冲 TRIG 给超声波测距模块,然后以 10us 为单位计算超声波测距模块返回的回响信号 ECHO 的高电平保持时间。ECHO 的高电平保持时间通过一定的换算后可以得到障碍物和超声波测距模块之间的距离(由距离公式计算&进制换算模块实现),我们将最终获得的以 mm 为单位的距离信息显示在 4 位数码管上。模块代码1、vlg_en模块 /* * @Author: Hangyu Liu * @Date: 2023-11-20 15:24:01 * @Email: hyliu@ee.ac.cn * @Descripttion: 板子时钟转化1us * @Last Modified time: 2023-11-20 15:24:01 */ //1us/50ns=20 module vlg_1us#(parameter P_CLK_PERIORD = 50) //i_clk的时钟周期50ns,20MHZ ( input i_clk, input i_rst_n, output reg o_clk //时钟周期1us ); parameter NUM_DIV = 20;// (1MHZ = 1us,20MHZ/20 = 1MHZ) reg [3:0] cnt; always @(posedge i_clk or negedge i_rst_n) begin if(!i_rst_n) begin cnt <= 4'd0; o_clk <= 1'b0; end else if(cnt == NUM_DIV/2 - 1) begin cnt <= 4'b0; o_clk <= ~o_clk; end else cnt <= cnt + 1'b1; end endmodule2、vlg_trig模块 /* * @Author: Hangyu Liu * @Date: 2023-11-20 16:50:44 * @Email: hyliu@ee.ac.cn * @Descripttion: 产生10us的触发超声信号 * @Last Modified time: 2023-11-20 16:50:44 */ module vlg_trig ( input i_rst_n, input clk_1us, //1us output reg o_trig ); reg[17:0] r_tricnt; //200ms的周期计数 1us一个单位 always @(posedge clk_1us or negedge i_rst_n)begin if(!i_rst_n) r_tricnt <= 18'd0; else if((r_tricnt == 18'd199999)) r_tricnt <= 18'd0; else r_tricnt <= r_tricnt + 1'b1; end //产生保持10us的高脉冲o_tring信号 always @(posedge clk_1us or negedge i_rst_n) begin if(!i_rst_n) o_trig<=1'b0; else if((r_tricnt > 18'd0) && (r_tricnt <= 18'd10)) o_trig <= 1'b1; //不从0开始0~9,防止出现不到10us的波干扰 else o_trig <= 1'b0; end endmodule3、vlg_echo模块module vlg_echo ( input i_clk, //1us input i_rst_n, input i_clk_1us, input i_echo, output reg[15:0] o_t_us ); reg[1:0] r_echo; wire pos_echo,neg_echo; reg r_cnt_en; reg[15:0] r_echo_cnt; //对i_echo信号同步处理,获取边沿检测信号,产生计数使能信号r_cnt_en always @(posedge i_clk or negedge i_rst_n) begin if(!i_rst_n) r_echo <= 2'd0; else r_echo <= ; //设置两个寄存器进行打拍寄存 end assign pos_echo = r_echo[0] & ~r_echo[1]; //现状态是1上状态是0,就是上升沿 assign neg_echo = ~r_echo[0] & r_echo[1]; always @(posedge i_clk or negedge i_rst_n) begin if(!i_rst_n) r_cnt_en <= 1'b0; else if(pos_echo) r_cnt_en <= 1'b1; else if(neg_echo) r_cnt_en <= 1'b0; else r_cnt_en <= r_cnt_en; end //对i_echo信号的高脉冲计时,以us为单位 always @(posedge i_clk_1us or negedge i_rst_n) begin if(!i_rst_n) r_echo_cnt <= 1'b0; else if(r_cnt_en) r_echo_cnt <= r_echo_cnt + 1'b1; else r_echo_cnt <= 1'b0; end //在下降沿对计数最大值进行保存 always @(negedge i_clk or negedge i_rst_n) begin if(!i_rst_n) o_t_us <= 16'd0; else if(neg_echo) o_t_us <= r_echo_cnt; else o_t_us <= o_t_us; end endmodule 4、顶层模块例化/*@Author: Hangyu Liu@Date: 2023-11-23 17:16:40@Email: hyliu@ee.ac.cn@Descripttion:HR04驱动模块@Last Modified time: 2023-11-23 17:16:40 */module vlg_design (input i_clk, //200MHZ input i_rst_n, input i_echo, //这是超声模块给的输入 output o_trig, output wire[15:0] w_t_us);wire clk_20MHZ;clk_div_20MHZ UU(.i_clk(i_clk), .i_rst_n(i_rst_n), .clk_div(clk_20MHZ));localparam P_CLK_PERIORD = 50;wire clk_1us;//使能时钟产生模块vlg_1us #(.P_CLK_PERIORD(P_CLK_PERIORD) //i_clk的时钟周期50ns,20MHZ)U1(.i_clk(clk_20MHZ), .i_rst_n(i_rst_n), .o_clk(clk_1us));//产生超声波测距模块的触发信号o_trigvlg_trig U2(.i_rst_n(i_rst_n), .clk_1us(clk_1us), .o_trig(o_trig));//超声波测距模块的回响信号i_echo的高电平时间采集vlg_echo U3(.i_clk(clk_20MHZ), .i_rst_n(i_rst_n), .i_clk_1us(clk_1us), .i_echo(i_echo), .o_t_us(w_t_us));endmodule## STM32(Cubemax)实现超声波测距 ### CubeMX配置STM32 1 时钟配置 这里我用的是STM32F103C8T6的核心板,时钟配置如下图,我用了8MHz的HSE,HCLK调到了最大值72MHz ![](https://pic.imgdb.cn/item/655b5cf4c458853aef446541.jpg) 2 设置输入捕获的定时器 设置定时器TIM2每1us向上计数一次,通道4为上升沿捕获并连接到超声波模块的ECHO引脚,记得开启定时器中断(涉及到捕获中断+定时器溢出中断)。 ![](https://pic.imgdb.cn/item/655b5d89c458853aef473cc2.jpg) 3 触发引脚 PB10接到了HC-SR04的TIRG触发引脚,默认输出低电平 ![](https://pic.imgdb.cn/item/655b5e9ac458853aef4c9def.jpg) 4 串口配置 还要开启一个串口,以便通过串口查看测距结果 ![](https://pic.imgdb.cn/item/655b5ecec458853aef4dbe35.jpg) ### 编写代码 hc-sr04.hifndef HCSR04_H_define HCSR04_H_include "main.h"include "delay.h"typedef struct{uint8_t edge_state; uint16_t tim_overflow_counter; uint32_t prescaler; uint32_t period; uint32_t t1; // 上升沿时间 uint32_t t2; // 下降沿时间 uint32_t high_level_us; // 高电平持续时间 float distance; TIM_TypeDef* instance;uint32_t ic_tim_ch;HAL_TIM_ActiveChannel active_channel;}Hcsr04InfoTypeDef;extern Hcsr04InfoTypeDef Hcsr04Info;/**@description: 超声波模块的输入捕获定时器通道初始化@param *htim@param Channel@return */void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel);/**@description: HC-SR04触发@param @return */void Hcsr04Start();/**@description: 定时器计数溢出中断处理函数@param main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef htim)@return */void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim);/**@description: 输入捕获计算高电平时间->距离@param main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef htim)@return */void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim);/**@description: 读取距离@param @return */float Hcsr04Read();endif / HCSR04_H_ /hc-sr04.cinclude "hc-sr04.h"Hcsr04InfoTypeDef Hcsr04Info;/**@description: 超声波模块的输入捕获定时器通道初始化@param *htim@param Channel@return */void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel) else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_2) else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_3) else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4) else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4) /--------[ Start The ICU Channel ]-------/ HAL_TIM_Base_Start_IT(htim); HAL_TIM_IC_Start_IT(htim, Channel);}/**@description: HC-SR04触发@param @return */void Hcsr04Start()/**@description: 定时器计数溢出中断处理函数@param main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef htim)@return */void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim)}/**@description: 输入捕获计算高电平时间->距离@param main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef htim)@return */void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim){ if((htim->Instance == Hcsr04Info.instance) && (htim->Channel == Hcsr04Info.active_channel)) {if(Hcsr04Info.edge_state == 0) // 捕获上升沿 { // 得到上升沿开始时间T1,并更改输入捕获为下降沿 Hcsr04Info.t1 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch); __HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_FALLING); Hcsr04Info.tim_overflow_counter = 0; // 定时器溢出计数器清零 Hcsr04Info.edge_state = 1; // 上升沿、下降沿捕获标志位 } else if(Hcsr04Info.edge_state == 1) // 捕获下降沿 { // 捕获下降沿时间T2,并计算高电平时间 Hcsr04Info.t2 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch); Hcsr04Info.t2 += Hcsr04Info.tim_overflow_counter * Hcsr04Info.period; // 需要考虑定时器溢出中断 Hcsr04Info.high_level_us = Hcsr04Info.t2 - Hcsr04Info.t1; // 高电平持续时间 = 下降沿时间点 - 上升沿时间点 // 计算距离 Hcsr04Info.distance = (Hcsr04Info.high_level_us / 1000000.0) * 340.0 / 2.0 * 100.0; // 重新开启上升沿捕获 Hcsr04Info.edge_state = 0; // 一次采集完毕,清零 __HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_RISING); }}}/**@description: 读取距离@param @return */float Hcsr04Read() return Hcsr04Info.distance;}main.c 1、引用对应的头文件/ USER CODE BEGIN Includes /include "hc-sr04.h"include "printf.h"/ USER CODE END Includes /2、200ms测距一次/**@brief The application entry point.@retval int */int main(void){ / USER CODE BEGIN 1 // USER CODE END 1 // MCU Configuration--------------------------------------------------------// Reset of all peripherals, Initializes the Flash interface and the Systick. / HAL_Init();/ USER CODE BEGIN Init // USER CODE END Init // Configure the system clock / SystemClock_Config();/ USER CODE BEGIN SysInit // USER CODE END SysInit // Initialize all configured peripherals / MX_GPIO_Init(); MX_TIM2_Init(); MX_USART1_UART_Init(); / USER CODE BEGIN 2 / DelayInit(72); Hcsr04Init(&htim2, TIM_CHANNEL_4); // 超声波模块初始化 Hcsr04Start(); // 开启超声波模块测距 printf("hc-sr04 start!\r\n"); / USER CODE END 2 // Infinite loop / / USER CODE BEGIN WHILE / while (1) {// 打印测距结果 printf("distance:%.1f cm\r\n", Hcsr04Read()); Hcsr04Start(); DelayMs(200); // 测距周期200ms /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */} / USER CODE END 3 /}3、重定义定时器的中断服务函数/ USER CODE BEGIN 4 //**@description: 定时器输出捕获中断@param *htim@return */void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)/**@description: 定时器溢出中断@param @return */void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)/ USER CODE END 4 /4、串口打印结果
2023年11月20日
489 阅读
0 评论
2 点赞
嵌入式/SOC开发利器-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设计,向下嵌入式、单片机什么的工作也能胜任。
2023年11月18日
1,042 阅读
0 评论
2 点赞
2023-11-01
机器学习/深度学习-10个激活函数详解
综述激活函数(Activation Function)是一种添加到人工神经网络中的函数,旨在帮助网络学习数据中的复杂模式。类似于人类大脑中基于神经元的模型,激活函数最终决定了要发射给下一个神经元的内容。在人工神经网络中,一个节点的激活函数定义了该节点在给定的输入或输入集合下的输出。标准的计算机芯片电路可以看作是根据输入得到开(1)或关(0)输出的数字电路激活函数。因此,激活函数是确定神经网络输出的数学方程式,本文概述了深度学习中常见的十种激活函数及其优缺点。首先我们来了解一下人工神经元的工作原理,大致如下:上述过程的数学可视化过程如下图所示:1. Sigmoid 激活函数Sigmoid 函数的图像看起来像一个 S 形曲线。函数表达式如下:$$\[\mathrm-z)}\]$$在什么情况下适合使用 Sigmoid 激活函数呢?Sigmoid 函数的输出范围是 0 到 1。由于输出值限定在 0 到 1,因此它对每个神经元的输出进行了归一化;用于将预测概率作为输出的模型。由于概率的取值范围是 0 到 1,因此 Sigmoid 函数非常合适;梯度平滑,避免「跳跃」的输出值;函数是可微的。这意味着可以找到任意两个点的 sigmoid 曲线的斜率;明确的预测,即非常接近 1 或 0。Sigmoid 激活函数有哪些缺点?倾向于梯度消失;函数输出不是以 0 为中心的,这会降低权重更新的效率;Sigmoid 函数执行指数运算,计算机运行得较慢。2. Tanh / 双曲正切激活函数tanh 激活函数的图像也是 S 形,表达式如下:$$f(x)=tanh(x)=\frac}-1$$tanh 是一个双曲正切函数。tanh 函数和 sigmoid 函数的曲线相对相似。但是它比 sigmoid 函数更有一些优势。首先,当输入较大或较小时,输出几乎是平滑的并且梯度较小,这不利于权重更新。二者的区别在于输出间隔,tanh 的输出间隔为 1,并且整个函数以 0 为中心,比 sigmoid 函数更好;在 tanh 图中,负输入将被强映射为负,而零输入被映射为接近零。注意:在一般的二元分类问题中,tanh 函数用于隐藏层,而 sigmoid 函数用于输出层,但这并不是固定的,需要根据特定问题进行调整。3. ReLU 激活函数ReLU 激活函数图像如上图所示,函数表达式如下:ReLU 函数是深度学习中较为流行的一种激活函数,相比于 sigmoid 函数和 tanh 函数,它具有如下优点:当输入为正时,不存在梯度饱和问题。计算速度快得多。ReLU 函数中只存在线性关系,因此它的计算速度比 sigmoid 和 tanh 更快当然,它也有缺点:Dead ReLU 问题。当输入为负时,ReLU 完全失效,在正向传播过程中,这不是问题。有些区域很敏感,有些则不敏感。但是在反向传播过程中,如果输入负数,则梯度将完全为零,sigmoid 函数和 tanh 函数也具有相同的问题;我们发现 ReLU 函数的输出为 0 或正数,这意味着 ReLU 函数不是以 0 为中心的函数。4. Leaky ReLU它是一种专门设计用于解决 Dead ReLU 问题的激活函数:ReLU vs Leaky ReLU为什么 Leaky ReLU 比 ReLU 更好?$$f(y_i)=\beginy_i,&\texty_i>0\\a_iy_i,&\texty_i\leq0\end.$$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)=\beginy_i,&\texty_i>0\\a_iy_i,&\texty_i\leq0\end.$$看一下 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),而无需更改隐藏容量或参数数量。Swish 激活函数的主要优点如下:「无界性」有助于防止慢速训练期间,梯度逐渐接近 0 并导致饱和;(同时,有界性也是有优势的,因为有界激活函数可以具有很强的正则化,并且较大的负输入问题也能解决);导数恒 > 0;平滑度在优化和泛化中起了重要作用。9. Maxout在 Maxout 层,激活函数是输入的最大值,因此只有 2 个 maxout 节点的多层感知机就可以拟合任意的凸函数。单个 Maxout 节点可以解释为对一个实值函数进行分段线性近似 (PWL) ,其中函数图上任意两点之间的线段位于图(凸函数)的上方。$ReLU=\max\bigl(0,x\bigr),\mathrm\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)。
2023年11月01日
210 阅读
0 评论
0 点赞
Python-BP神经网络实现单特征多分类
不同于图像处理,神经网络在处理其他不少领域,常常需要单特征分类,本例实现同半径的圆进行多分类(3分类),特征即为圆的半径。输入层12节点,一个6节点的隐藏层,输出层3个节点。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 = 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 = 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 = 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 = 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类。只要根据实际应用的需要修改特征数和分类数即可将上述程序应用于不同分类场景。
2023年10月17日
79 阅读
0 评论
1 点赞
2023-10-15
DL-神经网络的正向传播与反向传播
神经网络简述神经网络,就是在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}}$$因此,结果 a 就等于:这里再强调一遍,神经元的本质就是做非线性变换2.由神经元组成的神经网络神经元可以理解成一个函数,神经网络就是由很多个非线性变换的神经元组成的模型,因此神经网络可以理解成是一个非常复杂的复合函数。对于上图中的网络:(x1,x2,....,xn)为n维输入向量Wij,表示后一层低i个神经元与前一层第j个神经元之间的权值z = Wx + b是没有经过激活函数非线性变换之前的结果a=σ(z),是 z 经过激活函数非线性变换之后的结果\mathrm,为网络最终的输出结果。3.目标函数以折页损失为目标函数:$$\mathrm=\max(\Delta+s_-s,0)$$其中$\mathrm$。一般可以把$\Delta$固定下来,比如设为1或者10。一般来说,学习算法的学习过程就是优化调整参数,使得损失函数,或者说预测结果和实际结果的误差减小。BP算法其实是一个双向算法,包含两个步骤:1.正向传递输入信息,得到 Loss 值。2.反向传播误差,从而由优化算法来调整网络权值。4.求解损失函数对某个权值的梯度其中$\mathrm$。一般可以把$\Delta$固定下来,比如设为1或者10。对于上面的图, 假设图中指示出的网络中的某个权值 w$_\mathrm^\mathrm$ 发生了一个小的改变 $\Delta w_\mathrm^\mathrm$ , 假设网络最终损失函数的输出为$\mathbb$,则 C 应该是关于 $\mathrm_\mathrm^}$ 的一个复合函数。 所谓复合函数,就是把 $\mathbb$ 看成因变量,则$\mathrm_\mathrm^}$ 可以看成导致 C 改变的自变量, 比如假设有一个复 合函数 $\mathrm$,则 y 就好比这里的$\mathbb$ , x 就好比$\mathrm_}^}$, w$_^}$ 每经过一层网络可以看成是经过某个函数的处理。而下面求写的时候都用偏导数, 是因为虽然我们这里只关注了一个 w$_\mathrm^}$,但是实际上网络中的每一个 w 都可以看成一个 x。显然,这个$\Delta w_\mathrm^\mathrm$的变化会引起下一层直接与其相连的一个神经元,以及下一层之后所有神经元直到 最终输出 C 的变化, 如图中蓝线标记的就是该权值变化的影响传播路径。 把 C 的改变记为 $\Delta\mathbb$, 则根据高等数学中导数的知识可以得到:则神经元 a$_\mathrm^1$ 下面一层第 q 个与其相连的神经元 a$_}^$ 的变化为:$$ \Delta\mathrm}\approx\frac}}}\Delta\mathrm $$将 $(2)$ 代入 (3) 可以得到:$$ \Delta\mathrm^}\:\approx\:\frac^}}^}}\:\frac^}}^}}\:\Delta\mathrm^} $$假设从 $\mathrm^1$ 到 C的一条路径为$\mathrm^1,\mathrm^,...,\mathrm^},\mathrm^}$,则在该条路径上 C 的变化量 $\Delta C$ 为:$$\Delta\mathrm\approx\frac}_\mathrm^\mathrm}\frac_\mathrm^\mathrm}_\mathrm^\mathrm}\frac_\mathrm^\mathrm}_\mathrm^\mathrm}[USD3P]\frac_\mathrm^\mathrm}_\mathrm^\mathrm}\frac_\mathrm^\mathrm}_\mathrm^\mathrm}\Delta\mathrm_\mathrm^\mathrm$$至此,我们已经得到了一条路径上的变化量, 其实本质就是链式求导法则,或者说是复合函数求导法则。那么整个的变化量就县把所有可能链路上的变化量加起来:$$\Delta\mathrm\approx\sum_}\frac}^}}\frac^}}^}}\frac^}}^}}[USD3P]\frac^}}^}}\frac^}}^}}\Delta\mathrm^}$$$$\begin\text_}^&\text\\&\frac}_}^}\approx\sum_,<0}\frac}_}^}}\frac_}^}}_}^-1}}\frac_}^-1}}_}^-2}}[USD3P]\frac_}^+1}}_}^}}\frac_}^}}_}^}}\end$$到这里从数学分析的角度来说,我们可以知道这个梯度是可以计算和求解的。总结:1.每两个神经元之间是由一条边连接的,这个边就是一个权重值,它是后一个神经元 z 部分,也就是未经激活函数非线性变换之前的结果对前一个神经元的 a 部分,也就是经激活函数非线性变换之后的结果的偏导数。2.一条链路上所有偏导数的乘积就是这条路径的变化量。3.所有路径变化量之和就是整个损失函数的变化量。5.反向传播算法Backpropgation5.1 明确一些定义对于上面的神经网络, 首先明确下面一些定义:5.2 计算一个梯度假设损失函数为:J=(1+sc −s)来计第一下 $\frac}_^}$,由于 s 是网络的输出, $\frac}}=-1$, 所以只需要计算$\frac}_^}$ 即可。 而由于 s 激活函数就是1, 所以有:$$ \mathrm=z_1^=W_^a_1^+W_^a_2^} $$$$ \frac}_^}=\frac_^\mathrm_^+\mathrm_^\mathrm_^)}_^} $$所以:$$\frac_^}=\frac_^\mathrm_^}_^}=\mathrm_^\frac_^}_^}$$由于代入公式(9)可以得到$$\begin\frac}_^}=\mathrm_^\frac_^}_^}=\mathrm_^\frac_^}_^}\frac_^}_^1} \=\operatorname_^\mathfrak^(\mathfrak_^)\frac_^+\sum_}\mathfrak_}^\mathfrak_} ^ )}_^} \end$$$$ \text(10)中\frac_^+\sum_a_^W_^)}_^}=\mathrm_^。 $$此外还有一个更重要的变换, 同样的方法可以求出:$$ \frac}_1^}=\mathrm_^\mathrm^(\mathrm_1^) $$$$ \frac}_^}=\frac}_1^}\mathrm_4^=\delta_1^\mathrm_4^ $$并且结合前面对 $\delta_\mathrm^)}$ 的定义,公式 (10) 可以写成:所以损失函数对任离一个网络权伯的梯度可以弓成两个值相乘的形式。对于$\mathrm_4^$,它是网络前向传递过程中的一个神经元的输出, 我们当然可以在网络前向传递的时候将它保存下来。而对于 $\delta_1^$,它是反向传播过来的梯度,也就是 J 对 z 的梯度, 下面来者如何通过后面神经元的 $\delta_}^)}$ 反向传播得到 前一个神经元处的 $\delta_\mathrm^$ 。5.3 反向传播误差到这里需要注意,反向传播仅指用于计算梯度的方法,它并不是用于神经网络的整个学习算法。通过反向传播算法算法得到梯度之后,可以使用比如随机梯度下降算法等来进行学习。反向传播算法传播的是误差,传播方向是从最后一层依次往前,这是一个迭代的过程。在上面的过程中,我们求得了损失函数对于某个权值的梯度,通过该处的梯度值,可以将其向前传播,得到前一个结点的梯度。$(k-1)$例如对于上面的图, 假设已经求出 z$^)}$ 处的柠伊卡$^)}$,则行误差仅照某杀连接路径, 传递到 $\mathrm^]}$ per处,则该处的梯度为$\delta_}^)}W_}^-1)}$。实际上这只是一条路径, $\mathrm^)}$处可能会收到很多个不同的误差, 例如下面该神经元后面有两条权值 边的情况:这个时候只要把它们相加就好了,所以 a$_}^-1)}$处,则该处的梯度为$\sum_}\delta_}^)}\mathrm_}^-1)}$。6.优化示例再次说一下,反向传播仅指用于计算梯度的方法,它并不是用于神经网络的整个学习算法。通过反向传播算法算法得到梯度之后,还需要使用比如随机梯度下降算法等来进行学习。在上面的过程中, 我们通过反向传播弹法求解出了任意一个$z_j^)}$处的梯度$\delta_j^)}$,为什么是$z_j^)}$而不是$a_j^)}$,因为$z_j^)}$前面就是连接它的w,以随机梯度下降算法为例,w的优化方法为:$$\mathrm^=w_^-\eta\cdot\frac^}=w_^-\eta\cdot\delta_^\cdot a_^}$$
2023年10月15日
192 阅读
0 评论
2 点赞
RFID编码简介
信号编码系统包括信源编码和信道编码两大类,器作用是把要传输的信息尽可能的与传输信道相匹配,并提供对信息的某种保护以防止信息受到干扰。信源编码与信源译码的目的是提高信息传输的有效性以及完成模数转换等;信道编码与信道译码的目的是增强信号的抗干扰能力,提高传输的可靠性。常见的编码方法如下图:RFID系统常用编码方法:反向不归零(NRZ)编码曼彻斯特(Manchester)编码单极性归零(RZ)编码差动双相(DBP)编码密勒(Miller)编码和差动编码1、反向不归零编码(NRZ,Non Return Zero)反向不归零编码用高电平表示二进制“1”,低电平表示二进制“0”,如下图所示:此码型不宜传输,有以下原因有直流,一般信道难于传输零频附近的频率分量;接收端判决门限与信号功率有关,不方便使用;不能直接用来提取位同步信号,因为NRZ中不含有位同步信号频率成分;要求传输线有一根接地。注:ISO14443 TYPE B协议中电子标签和阅读器传递数据时均采用NRZ2、曼彻斯特编码(Manchester)曼彻斯特编码也被称为分相编码(Split-Phase Coding)。某比特位的值是由该比特长度内半个比特周期时电平的变化(上升或下降)来表示的,在半个比特周期时的负跳变表示二进制“1”,半个比特周期时的正跳变表示二进制“0”,如下图所示:曼彻斯特编码的特点曼彻斯特编码在采用负载波的负载调制或者反向散射调制时,通常用于从电子标签到读写器的数据传输,因为这有利于发现数据传输的错误。这是因为在比特长度内,“没有变化”的状态是不允许的。当多个标签同时发送的数据位有不同值时,则接收的上升边和下降边互相抵消,导致在整个比特长度内是不间断的负载波信号,由于该状态不允许,所以读写器利用该错误就可以判定碰撞发生的具体位置。曼彻斯特编码由于跳变都发生在每一个码元中间,接收端可以方便地利用它作为同步时钟。注:ISO14443 TYPE A协议中电子标签向阅读器传递数据时采用曼彻斯特编码。ISO18000-6 TYPE B 读写器向电子标签传递数据时采用的是曼彻斯特编码3、单极性归零编码(Unipolar RZ)当发码1时发出正电流,但正电流持续的时间短于一个码元的时间宽度,即发出一个窄脉冲当发码0时,完全不发送电流单极性归零编码可用来提取位同步信号。4、差动双相编码(DBP)差动双相编码在半个比特周期中的任意的边沿表示二进制“0”,而没有边沿就是二进制“1”,如下图所示。此外在每个比特周期开始时,电平都要反相。因此,对于接收器来说,位节拍比较容易重建。5、密勒编码(Miller)密勒编码在半个比特周期内的任意边沿表示二进制“1”,而经过下一个比特周期中不变的电平表示二进制“0”。一连串的比特周期开始时产生电平交变,如下图所示,因此,对于接收器来说,位节拍也比较容易重建。6、修正密勒码编码7、脉冲-间歇编码对于脉冲—间歇编码来说,在下一脉冲前的暂停持续时间t表示二进制“1”,而下一脉冲前的暂停持续时间2t则表示二进制“0”,如下图所示。这种编码方法在电感耦合的射频系统中用于从读写器到电子标签的数据传输,由于脉冲转换时间很短,所以就可以在数据传输过程中保证从读写器的高频场中连续给射频标签供给能量。8、脉冲位置编码(PPM,Pulse Position Modulation)脉冲位置编码与上述的脉冲间歇编码类似,不同的是,在脉冲位置编码中,每个数据比特的宽度是一致的。其中,脉冲在第一个时间段表示“00”,第二个时间段表示“01”, 第三个时间段表示“10”, 第四个时间段表示“11”, 如图所示注:ISO15693协议中,数据编码采用PPM9、FM0编码FM0(即Bi-Phase Space)编码的全称为双相间隔码编码、工作原理是在一个位窗内采用电平变化来表示逻辑。如果电平从位窗的起始处翻转,则表示逻辑“1”。如果电平除了在位窗的起始处翻转,还在位窗中间翻转则表示逻辑“0”。注:ISO18000-6 typeA 由标签向阅读器的数据发送采用FM0编码10、PIE编码PIE(Pulse interval encoding)编码的全称为脉冲宽度编码,原理是通过定义脉冲下降沿之间的不同时间宽度来表示数据。在该标准的规定中,由阅读器发往标签的数据帧由SOF(帧开始信号)、EOF(帧结束信号)、数据0和1组成。在标准中定义了一个名称为“Tari”的时间间隔,也称为基准时间间隔,该时间段为相邻两个脉冲下降沿的时间宽度,持续为25μs。注:ISO18000-6 typeA 由阅读器向标签的数据发送采用PIE编码=============================================注:选择编码方法的考虑因素编码方式的选择要考虑电子标签能量的来源在REID系统中使用的电子标签常常是无源的,而无源标签需要在读写器的通信过程中获得自身的能量供应。为了保证系统的正常工作,信道编码方式必须保证不能中断读写器对电子标签的能量供应。在RFID系统中,当电子标签是无源标签时,经常要求基带编码在每两个相邻数据位元间具有跳变的特点,这种相邻数据间有跳变的码,不仅可以保证在连续出现“0”时对电子标签的能量供应,而且便于电子标签从接收到的码中提取时钟信息。编码方式的选择要考虑电子标签的检错的能力出于保障系统可靠工作的需要,还必须在编码中提供数据一级的校验保护,编码方式应该提供这种功能。可以根据码型的变化来判断是否发生误码或有电子标签冲突发生。在实际的数据传输中,由于信道中干扰的存在,数据必然会在传输过程中发生错误,这时要求信道编码能够提供一定程度的检测错误的能力。曼彻斯特编码、差动双向编码、单极性归零编码具有较强的编码检错能力。编码方式的选择要考虑电子标签时钟的提取在电子标签芯片中,一般不会有时钟电路,电子标签芯片一般需要在读写器发来的码流中提取时钟。曼彻斯特编码、密勒编码、差动双向编码容易使电子标签提取时钟。
2023年09月27日
949 阅读
0 评论
0 点赞
2023-07-27
一张图看懂数字IC设计前后端全流程(DC ICC PT的关系)
关系图DC综合后用PrimeTime做一遍STADC的时候,通过SDC定义了很多约束,留下了很多Margin为后端,DC综合的网表是理想的状态。后端会进行Place和Route并进行CTS,这才接近于真实的电路,后端会通过QRC吐出SPEF, SPEF在转成sdf,供PT分析.此时PT分析的已经不是综合之后的网表,PT分析的是经过PR之后且CTS之后的网表。目的就是看在经过后端处理之后时序是否还signoff。前端DC综合的时候,本身也会进行timing分析,有些路径时序不收敛,DC也会报出来,如果确认这是一条真的路径. 这样你就要改RTL了. DC自己都报时序不收敛,后面也都没有做的必要了。
2023年07月27日
1,311 阅读
0 评论
1 点赞
1
2
3
...
26