刘航宇 发布的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🖼️相册
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
10,783 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
6,198 阅读
3
【高数】形心计算公式讲解大全
5,628 阅读
4
【1】基于STM32CubeMX-STM32GPIO端口开发
4,314 阅读
5
如何判断运放是工作在线性区还是非线性区
4,100 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
电路&嵌入式
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
小实验
软件算法
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇Hangyu Liu
嵌入式系统&数字IC爱好者博客
累计撰写
296
篇文章
累计收到
520
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
电路&嵌入式
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🖼️相册
🎵音乐
🏞️壁纸
用户登录
登录
刘航宇(共296篇)
找到
296
篇与
刘航宇
相关的结果
嵌入式/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日
294 阅读
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日
83 阅读
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日
47 阅读
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日
100 阅读
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日
508 阅读
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日
697 阅读
0 评论
1 点赞
2023-07-23
Python机器学习- 鸢尾花分类
1、描述请编写代码实现train_and_predict功能,实现能够根据四个特征对三种类型的鸢尾花进行分类。train_and_predict函数接收三个参数:train_input_features—二维NumPy数组,其中每个元素都是一个数组,它包含:萼片长度、萼片宽度、花瓣长度和花瓣宽度。train_outputs—一维NumPy数组,其中每个元素都是一个数字,表示在train_input_features的同一行中描述的鸢尾花种类。0表示鸢尾setosa,1表示versicolor,2代表Iris virginica。prediction_features—二维NumPy数组,其中每个元素都是一个数组,包含:萼片长度、萼片宽度、花瓣长度和花瓣宽度。该函数使用train_input_features作为输入数据,使用train_outputs作为预期结果来训练分类器。请使用训练过的分类器来预测prediction_features的标签,并将它们作为可迭代对象返回(如list或numpy.ndarray)。结果中的第n个位置是prediction_features参数的第n行。2、code# 导入numpy库,用于处理多维数组 import numpy as np # 导入sklearn库中的数据集、模型选择、度量和朴素贝叶斯模块 from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn import metrics from sklearn.naive_bayes import GaussianNB # 定义train_and_predict函数,接收三个参数:训练输入特征、训练输出标签和预测输入特征 def train_and_predict(train_input_features, train_outputs, prediction_features): # 创建一个高斯朴素贝叶斯分类器对象 clf = GaussianNB() # 使用训练输入特征和训练输出标签来训练分类器 clf.fit(train_input_features, train_outputs) # 使用预测输入特征来预测输出标签,并将结果返回 y_pred = clf.predict(prediction_features) return y_pred # 加载鸢尾花数据集,包含150个样本,每个样本有四个特征和一个标签 iris = datasets.load_iris() # 将数据集随机分成训练集和测试集,其中训练集占70%,测试集占30%,并设置随机种子为0 X_train, X_test, y_train, y_test = train_test_split( iris.data, iris.target, test_size=0.3, random_state=0 ) # 调用train_and_predict函数,使用训练集来训练分类器,并使用测试集来预测标签,将结果赋值给y_pred y_pred = train_and_predict(X_train, y_train, X_test) # 如果y_pred不为空,打印预测标签和真实标签的准确率,即正确预测的比例 if y_pred is not None: print(metrics.accuracy_score(y_test, y_pred))3、描述机器学习库 sklearn 自带鸢尾花分类数据集,分为四个特征和三个类别,其中这三个类别在数据集中分别表示为 0, 1 和 2,请实现 transform_three2two_cate 函数的功能,该函数是一个无参函数,要求将数据集中 label 为 2 的数据进行移除,也就是说仅保留 label 为 0 和为 1 的情况,并且对 label 为 0 和 1 的特征数据进行保留,返回值为 numpy.ndarray 格式的训练特征数据和 label 数据,分别为命名为 new_feat 和 new_label。然后在此基础上,实现 train_and_evaluate 功能,并使用生成的 new_feat 和 new_label 数据集进行二分类训练,限定机器学习分类器只能从逻辑回归和决策树中进行选择,将训练数据和测试数据按照 8:2 的比例进行分割。要求输出测试集上的 accuracy_score,同时要求 accuracy_score 要不小于 0.95。4、code#导入numpy库,它是一个提供了多维数组和矩阵运算等功能的Python库 import numpy as np #导入sklearn库中的datasets模块,它提供了一些内置的数据集 from sklearn import datasets #导入sklearn库中的model_selection模块,它提供了一些用于模型选择和评估的工具,比如划分训练集和测试集 from sklearn.model_selection import train_test_split #导入sklearn库中的preprocessing模块,它提供了一些用于数据预处理的工具,比如归一化 from sklearn.preprocessing import MinMaxScaler #导入sklearn库中的linear_model模块,它提供了一些线性模型,比如逻辑回归 from sklearn.linear_model import LogisticRegression #导入sklearn库中的metrics模块,它提供了一些用于评估模型性能的指标,比如F1分数、ROC曲线面积、准确率等 from sklearn.metrics import f1_score,roc_auc_score,accuracy_score #导入sklearn库中的tree模块,它提供了一些树形模型,比如决策树 from sklearn.tree import DecisionTreeClassifier #定义一个函数transform_three2two_cate,它的作用是将鸢尾花数据集中的三分类问题转化为二分类问题 def transform_three2two_cate(): #从datasets模块中加载鸢尾花数据集,并赋值给data变量 data = datasets.load_iris() #其中data特征数据的key为data,标签数据的key为target #需要取出原来的特征数据和标签数据,移除标签为2的label和特征数据,返回值new_feat为numpy.ndarray格式特征数据,new_label为对应的numpy.ndarray格式label数据 #需要注意特征和标签的顺序一致性,否则数据集将混乱 #code start here #使用numpy库中的where函数找出标签为2的索引,并赋值给index_arr变量 index_arr = np.where(data.target == 2)[0] #使用numpy库中的delete函数删除特征数据中对应索引的行,并赋值给new_feat变量 new_feat = np.delete(data.data, index_arr, 0) #使用numpy库中的delete函数删除标签数据中对应索引的元素,并赋值给new_label变量 new_label = np.delete(data.target, index_arr) #code end here #返回新的特征数据和标签数据 return new_feat,new_label #定义一个函数train_and_evaluate,它的作用是用决策树分类器来训练和评估鸢尾花数据集 def train_and_evaluate(): #调用transform_three2two_cate函数,得到新的特征数据和标签数据,并赋值给data_X和data_Y变量 data_X,data_Y = transform_three2two_cate() #使用train_test_split函数,将数据集划分为训练集和测试集,其中测试集占20%,并赋值给train_x,test_x,train_y,test_y变量 train_x,test_x,train_y,test_y = train_test_split(data_X,data_Y,test_size = 0.2) #已经划分好训练集和测试集,接下来请实现对数据的训练 #code start here #创建一个决策树分类器的实例,并赋值给estimator变量 estimator = DecisionTreeClassifier() #使用fit方法,用训练集的特征和标签来训练决策树分类器 estimator.fit(train_x, train_y) #使用predict方法,用测试集的特征来预测标签,并赋值给y_predict变量 y_predict = estimator.predict(test_x) #code end here #注意模型预测的label需要定义为 y_predict,格式为list或numpy.ndarray #使用accuracy_score函数,计算测试集上的准确率分数,并打印出来 print(accuracy_score(y_predict,test_y)) #如果这个文件是作为主程序运行,则执行以下代码 if __name__ == "__main__": #调用train_and_evaluate函数 train_and_evaluate() #要求执行train_and_evaluate()后输出为: #1、,代表数据label为0和1 #2、测试集上的准确率分数,要求>0.95
2023年07月23日
122 阅读
0 评论
0 点赞
算法-反转链表C&Python实现
描述给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。数据范围: 0≤n≤1000要求:空间复杂度 O(1) ,时间复杂度 O(n) 。如当输入链表时,经反转后,原链表变为,所以对应的输出为。以上转换过程如下图所示:基础数据结构知识回顾空间复杂度 O (1) 表示算法执行所需要的临时空间不随着某个变量 n 的大小而变化,即此算法空间复杂度为一个常量,可表示为 O (1)。例如,下面的代码中,变量 i、j、m 所分配的空间都不随着 n 的变化而变化,因此它的空间复杂度是 O (1)。int i = 1; int j = 2; ++i; j++; int m = i + j;时间复杂度 O (n) 表示算法执行的时间与 n 成正比,即此算法时间复杂度为线性阶,可表示为 O (n)。例如,下面的代码中,for 循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此这类代码都可以用 O (n) 来表示它的时间复杂度。for (i=1; i<=n; ++i) { j = i; j++; }题解C++篇可以先用一个vector将单链表的指针都存起来,然后再构造链表。此方法简单易懂,代码好些。// 定义一个Solution类 class Solution { public: // 定义一个函数,接收一个链表的头节点指针,返回一个反转后的链表的头节点指针 ListNode* ReverseList(ListNode* pHead) { // 如果头节点指针为空,直接返回空指针 if (!pHead) return nullptr; // 定义一个vector,用于存储链表中的每个节点指针 vector<ListNode*> v; // 遍历链表,将每个节点指针放入vector中 while (pHead) { v.push_back(pHead); pHead = pHead->next; } // 反转vector,也可以逆向遍历 reverse(v.begin(), v.end()); // 取出vector中的第一个元素,作为反转后的链表的头节点指针 ListNode *head = v[0]; // 定义一个当前节点指针,初始化为头节点指针 ListNode *cur = head; // 从第二个元素开始遍历vector,构造反转后的链表 for (int i=1; i<v.size(); ++i) { // 当前节点的下一个指针指向下一个节点 cur->next = v[i]; // 当前节点后移 cur = cur->next; } // 切记最后一个节点的下一个指针指向nullptr cur->next = nullptr; // 返回反转后的链表的头节点指针 return head; } };初始化:3个指针1)pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr2)cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head3)nex指针指向待反转链表的第二个节点,目的是保存链表,因为cur改变指向后,后面的链表则失效了,所以需要保存接下来,循环执行以下三个操作1)nex = cur->next, 保存作用2)cur->next = pre 未反转链表的第一个节点的下个指针指向已反转链表的最后一个节点3)pre = cur, cur = nex; 指针后移,操作下一个未反转链表的第一个节点循环条件,当然是cur != nullptr循环结束后,cur当然为nullptr,所以返回pre,即为反转后的头结点这里以1->2->3->4->5 举例:// 定义一个Solution类 class Solution { public: // 定义一个函数,接收一个链表的头节点指针,返回一个反转后的链表的头节点指针 ListNode* ReverseList(ListNode* pHead) { // 定义一个前驱节点指针,初始化为nullptr ListNode *pre = nullptr; // 定义一个当前节点指针,初始化为头节点指针 ListNode *cur = pHead; // 定义一个后继节点指针,初始化为nullptr ListNode *nex = nullptr; // 遍历链表,反转每个节点的指向 while (cur) { // 记录当前节点的下一个节点 nex = cur->next; // 将当前节点的下一个指针指向前驱节点 cur->next = pre; // 将前驱节点更新为当前节点 pre = cur; // 将当前节点更新为后继节点 cur = nex; } // 返回反转后的链表的头节点指针,即原链表的尾节点指针 return pre; } };题解Python篇假设 链表为 1->2->3->4->null 空就是链表的尾obj: 4->3->2->1->null那么逻辑是首先设定待反转链表的尾 pre = nonehead 代表一个动态的表头 逐步取下一次链表的值然后利用temp保存 head.next 第一次迭代head为1 temp 为2 原始链表中是1->2现在我们需要翻转 即 令head.next = pre 实现 1->none但此时链表切断了 变成了 1->none 2->3->4所以我们要移动指针,另pre = head 也就是pre从none 变成1 下一次即可完成2->1的链接此外另head = next 也就是说 把指针移动到后面仍然链接的链表上这样执行下一次循环 则实现 把2->3 转变为 2->1->none然后再次迭代直到最后一次 head 变成了none 而pre变成了4 则pre是新的链表的表头完成翻转# -*- coding:utf-8 -*- # 定义一个ListNode类,表示链表中的节点 # class ListNode: # def __init__(self, x): # self.val = x # 节点的值 # self.next = None # 节点的下一个指针 # 定义一个Solution类,用于解决问题 class Solution: # 定义一个函数,接收一个链表的头节点,返回一个反转后的链表的头节点 def ReverseList(self, pHead): # write code here pre = None # 定义一个前驱节点,初始化为None head = pHead # 定义一个当前节点,初始化为头节点 while head: # 遍历链表,反转每个节点的指向 temp = head.next # 记录当前节点的下一个节点 head.next = pre # 将当前节点的下一个指针指向前驱节点 pre = head # 将前驱节点更新为当前节点 head = temp # 将当前节点更新为下一个节点 return pre # 返回反转后的链表的头节点,即原链表的尾节点
2023年07月22日
48 阅读
0 评论
1 点赞
2023-07-21
嵌入式软件-无需排序找数字
题目有1000个整数,每个数字都在1~200之间,数字随机排布。假设不允许你使用任何排序方法将这些整数有序化,你能快速找到从0开始的第450小的数字吗?(从小到大第450位)示例[184, 87, 178, 116, 194, 136, 187, 93, 50, 22, 163, 28, 91, 60, 164, 127, 141, 27, 173, 137, 12, 169, 168, 30, 183, 131, 63, 124, 68, 136, 130, 3, 23, 59, 70, 168, 194, 57, 12, 43, 30, 174, 22, 120, 185, 138, 199, 125, 116, 171, 14, 127, 92, 181, 157, 74, 63, 171, 197, 82, 106, 126, 85, 128, 137, 106, 47, 130, 114, 58, 125, 96, 183, 146, 15, 168, 35, 165, 44, 151, 88, 9, 77, 179, 189, 185, 4, 52, 155, 200, 133, 61, 77, 169, 140, 13, 27, 187, 95, 140, 196, 171, 35, 179, 68, 2, 98, 103, 118, 93, 53, 157, 102, 81, 87, 42, 66, 90, 45, 20, 41, 130, 32, 118, 98, 172, 82, 76, 110, 128, 168, 57, 98, 154, 187, 166, 107, 84, 20, 25, 129, 72, 133, 30, 104, 20, 71, 169, 109, 116, 141, 150, 197, 124, 19, 46, 47, 52, 122, 156, 180, 89, 165, 29, 42, 151, 194, 101, 35, 165, 125, 115, 188, 57, 144, 92, 28, 166, 60, 137, 33, 152, 38, 29, 76, 8, 75, 122, 59, 196, 30, 38, 36, 194, 19, 29, 144, 12, 129, 130, 177, 5, 44, 164, 14, 139, 7, 41, 105, 19, 129, 89, 170, 118, 118, 197, 125, 144, 71, 184, 91, 100, 173, 126, 45, 191, 106, 140, 155, 187, 70, 83, 143, 65, 198, 108, 156, 5, 149, 12, 23, 29, 100, 144, 147, 169, 141, 23, 112, 11, 6, 2, 62, 131, 79, 106, 121, 137, 45, 27, 123, 66, 109, 17, 83, 59, 125, 38, 63, 25, 1, 37, 53, 100, 180, 151, 69, 72, 174, 132, 82, 131, 134, 95, 61, 164, 200, 182, 100, 197, 160, 174, 14, 69, 191, 96, 127, 67, 85, 141, 91, 85, 177, 143, 137, 108, 46, 157, 180, 19, 88, 13, 149, 173, 60, 10, 137, 11, 143, 188, 7, 102, 114, 173, 122, 56, 20, 200, 122, 105, 140, 12, 141, 68, 106, 29, 128, 151, 185, 59, 121, 25, 23, 70, 197, 82, 31, 85, 93, 173, 73, 51, 26, 186, 23, 100, 41, 43, 99, 114, 99, 191, 125, 191, 10, 182, 20, 137, 133, 156, 195, 5, 180, 170, 74, 177, 51, 56, 61, 143, 180, 85, 194, 6, 22, 168, 105, 14, 162, 155, 127, 60, 145, 3, 3, 107, 185, 22, 43, 69, 129, 190, 73, 109, 159, 99, 37, 9, 154, 49, 104, 134, 134, 49, 91, 155, 168, 147, 169, 130, 101, 47, 189, 198, 50, 191, 104, 34, 164, 98, 54, 93, 87, 126, 153, 197, 176, 189, 158, 130, 37, 61, 15, 122, 61, 105, 29, 28, 51, 149, 157, 103, 195, 98, 100, 44, 40, 3, 29, 4, 101, 82, 48, 139, 160, 152, 136, 135, 140, 93, 16, 128, 105, 30, 50, 165, 86, 30, 144, 136, 178, 101, 39, 172, 150, 90, 168, 189, 93, 196, 144, 145, 30, 191, 83, 141, 142, 170, 27, 33, 62, 43, 161, 118, 24, 162, 82, 110, 191, 26, 197, 168, 78, 35, 91, 27, 125, 58, 15, 169, 6, 159, 113, 187, 101, 147, 127, 195, 117, 153, 179, 130, 147, 91, 48, 171, 52, 81, 32, 194, 58, 28, 113, 87, 15, 156, 113, 91, 13, 80, 11, 170, 190, 75, 156, 42, 21, 34, 188, 89, 139, 167, 171, 85, 57, 18, 7, 61, 50, 38, 6, 60, 18, 119, 146, 184, 74, 59, 74, 38, 90, 84, 8, 79, 158, 115, 72, 130, 101, 60, 19, 39, 26, 189, 75, 34, 158, 82, 94, 159, 71, 100, 18, 40, 170, 164, 23, 195, 174, 48, 32, 63, 83, 191, 93, 192, 58, 116, 122, 158, 175, 92, 148, 152, 32, 22, 138, 141, 55, 31, 99, 126, 82, 117, 117, 3, 32, 140, 197, 5, 139, 181, 19, 22, 171, 63, 13, 180, 178, 86, 137, 105, 177, 84, 8, 160, 58, 145, 100, 112, 128, 151, 37, 161, 19, 106, 164, 50, 45, 112, 6, 135, 92, 176, 156, 15, 190, 169, 194, 119, 6, 83, 23, 183, 118, 31, 94, 175, 127, 194, 87, 54, 144, 75, 15, 114, 180, 178, 163, 176, 89, 120, 111, 133, 95, 18, 147, 36, 138, 92, 154, 144, 174, 129, 126, 92, 111, 19, 18, 37, 164, 56, 91, 59, 131, 105, 172, 62, 34, 86, 190, 74, 5, 52, 6, 51, 69, 104, 86, 7, 196, 40, 150, 121, 168, 27, 164, 78, 197, 182, 66, 161, 37, 156, 171, 119, 12, 143, 133, 197, 180, 122, 71, 185, 173, 28, 35, 41, 84, 73, 199, 31, 64, 148, 151, 31, 174, 115, 60, 123, 48, 125, 83, 36, 33, 5, 155, 44, 99, 87, 41, 79, 160, 63, 63, 84, 42, 49, 124, 125, 73, 123, 155, 136, 22, 58, 166, 148, 172, 177, 70, 19, 102, 104, 54, 134, 108, 160, 129, 7, 198, 121, 85, 109, 135, 99, 192, 177, 99, 116, 53, 172, 190, 160, 107, 11, 17, 25, 110, 140, 1, 179, 110, 54, 82, 115, 139, 190, 27, 68, 148, 24, 188, 32, 133, 123, 82, 76, 51, 180, 191, 55, 151, 132, 14, 58, 95, 182, 82, 4, 121, 34, 183, 182, 88, 16, 97, 26, 5, 123, 93, 152, 98, 33, 135, 182, 107, 16, 58, 109, 196, 200, 163, 98, 84, 177, 155, 178, 110, 188, 133, 183, 22, 67, 164, 61, 83, 12, 86, 87, 86, 131, 191, 184, 115, 77, 117, 21, 93, 126, 129, 40, 126, 91, 137, 161, 19, 44, 138, 129, 183, 22, 111, 156, 89, 26, 16, 171, 38, 54, 9, 123, 184, 151, 58, 98, 28, 127, 70, 72, 52, 150, 111, 129, 40, 199, 89, 11, 194, 178, 91, 177, 200, 153, 132, 88, 178, 100, 58, 167, 153, 18, 42, 136, 169, 99, 185, 196, 177, 6, 67, 29, 155, 129, 109, 194, 79, 198, 156, 73, 175, 46, 1, 126, 198, 84, 13, 128, 183, 22]94解答解法一:1、不能排序2、找从0开始的第450位小的数,注意的“从0开始”这句话。[0-450]这个区间总共有451个数,因此我们需要找的是第451位小的数开始做题----------------------------------------------------------可以利用hash表的特性,使用一个201大小的数组,数组的下标为数据的值,数组的值为数据出现的次数。可以这么理解key->代表数据,同时也是数组下标value->代表数据出现的次数首先给数组元素初始化为0,也就是每个数据出现的次数都是0。接着使用循环将每个数据出现的次数添加到数组中再利用循环将出现的次数累加,如果次数累加到450,就说明找到了第450大的数code1/* 1、定义一个大小为201的整型数组arr,用来存储每个数在数组numbers中出现的次数。使用memset函数将所有元素初始化为0。 2、定义一个整型变量i,用来作为循环的计数器。初始化为0。 3、使用while循环遍历数组numbers,对于每个数,将其作为arr的下标,将arr对应的元素加一,表示该数出现了一次。同时将i加一,表示下一个数。 4、重新将i赋值为1,表示从第一个数开始计算出现次数之和。 5、定义一个整型变量sum,用来累计前面的数出现的次数之和。初始化为0。 6、使用while循环遍历arr,从下标1开始,对于每个元素,将其加到sum上,然后判断sum是否大于或等于451。如果是,则跳出循环,表示找到了满足条件的数。如果不是,则继续遍历。 7、返回i,表示找到的数。 */ int find(int* numbers, int numbersLen ) { // write code here int arr[201], i=0, sum=0; //定义一个大小为201的整型数组arr,用来存储每个数在数组numbers中出现的次数。定义一个整型变量i,用来作为循环的计数器。定义一个整型变量sum,用来累计前面的数出现的次数之和。 //初始化数组元素 memset(arr,0,sizeof(arr)); //使用memset函数将所有元素初始化为0。 //循环添加每个数据出现的次数 while(i < numbersLen){ //使用while循环遍历数组numbers arr[numbers[i]]++; //对于每个数,将其作为arr的下标,将arr对应的元素加一,表示该数出现了一次。 i++; //同时将i加一,表示下一个数。 } //循环计算次数,当次数超过451次,那就是找到了 i=1; //重新将i赋值为1,表示从第一个数开始计算出现次数之和。 while((sum=sum+arr[i]) < 451){ //使用while循环遍历arr,从下标1开始 i++; //对于每个元素,将其加到sum上,并将i加一。 } return i; //返回i,表示找到的数。 } code2解法二:因为知道每个数字的大小:1~200,所以无论序列有多少个数字,可以根据一个200行的表,然后统计所有数字出现的频率。这个思路在硬件设计上常见,即用数字的值代表查表的地址。/* 1、定义一个大小为201的整型数组table,用来存储每个数在数组numbers中出现的次数。初始化为0。 2、遍历数组numbers,对于每个数,将其作为table的下标,将table对应的元素加一,表示该数出现了一次。 3、定义一个整型变量acc,用来累计前面的数出现的次数之和。初始化为0。 4、遍历table,从下标1开始,对于每个元素,将其加到acc上,然后判断acc是否大于或等于451。如果是,则返回当前的下标,表示找到了满足条件的数。如果不是,则继续遍历。 5、如果遍历完table都没有找到满足条件的数,则返回0。 */ /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param numbers int整型一维数组 * @param numbersLen int numbers数组长度 * @return int整型 */ int find(int* numbers, int numbersLen ) { // write code here int table[201] = ; //定义一个大小为201的整型数组table,用来存储每个数在数组numbers中出现的次数。初始化为0。 for (int i = 0; i < numbersLen; i++) { table[numbers[i]]++; //遍历数组numbers,对于每个数,将其作为table的下标,将table对应的元素加一,表示该数出现了一次。 } int acc = 0; //定义一个整型变量acc,用来累计前面的数出现的次数之和。初始化为0。 for (int i = 1; i < 201; i++) { acc += table[i]; //遍历table,从下标1开始,对于每个元素,将其加到acc上。 if (acc >= 451) return i; //判断acc是否大于或等于451。如果是,则返回当前的下标,表示找到了满足条件的数。 } return 0; //如果遍历完table都没有找到满足条件的数,则返回0。 }
2023年07月21日
72 阅读
0 评论
0 点赞
2023-07-19
嵌入式软件-基于C语言小端转大端
意义大端小端转化对嵌入式系统有意义,因为不同的处理器或者通信协议可能采用不同的字节序来存储或者传输数据。字节序是指一个多字节数据在内存中的存放顺序,它有两种主要的形式:大端:最高有效位(MSB)存放在最低的内存地址,最低有效位(LSB)存放在最高的内存地址。小端:最低有效位(LSB)存放在最低的内存地址,最高有效位(MSB)存放在最高的内存地址。例如,一个32位的整数0x12345678,在大端系统中,它的内存布局是:而在小端系统中,它的内存布局是:如果一个嵌入式系统需要和不同字节序的设备或者网络进行交互,就需要进行字节序的转换,否则会导致数据错误或者通信失败。例如,TCP/IP协议族中的所有层都采用大端字节序来表示数据包头中的16位或32位的值,如IP地址、包长、校验和等。如果一个嵌入式系统使用小端字节序的处理器,并且想要建立一个TCP连接,就需要将IP地址等信息从小端转换为大端再发送出去,否则对方无法正确解析。题目输入一个数字n,假设它是以小端模式保存在机器的,请将其转换为大端方式保存时的值。示例输入:1返回值:16777216解答1.char指针,按字节替换/* * @param n int整型 * @return int整型 */ int convert(int n ) { // write code here int tmp = 0x00000000; //开辟新的int空间用于接收转化结果 unsigned char *p = &tmp, *q = &n; p[0] = q[3]; p[1] = q[2]; p[2] = q[1]; p[3] = q[0]; return tmp; }2.利用union联合体共用内存空间特性,使用char数组来改变/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @return int整型 */ typedef union { int i; unsigned char c[4] } inc_u; int convert(int n ) { // write code here inc_u x; //这里也可以用新开辟空间进行置换 x.i = n; //利用按位异或运算可叠加、可还原性 x.c[0] ^= x.c[3], x.c[3] ^= x.c[0], x.c[0] ^= x.c[3]; //首尾两字节对调 x.c[1] ^= x.c[2], x.c[2] ^= x.c[1], x.c[1] ^= x.c[2]; //中间两字节对调 return x.i; /* 按位<<到正确位置,并用|拼装 return (x.c[0]<<24)|(x.c[1]<<16)|(x.c[2]<<8)|x.c[3]; */ }3. 使用按位与运算保留以获取每个字节,然后按位左移到正确位置并拼接/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @return int整型 */ int convert(int n ) { // write code here return (((n & 0xff000000)>>24) | ((n & 0x00ff0000)>>8 ) | ((n & 0x0000ff00)<<8 ) | ((n & 0x000000ff)<<24); //按位与时,遇0清零,遇1保留 ); }4. 使用预定义好的宏函数本条方法参考 https://www.codeproject.com/Articles/4804/Basic-concepts-on-Endianness文中提到网络上常用的套接字接口(socket API)指定了一种称为网络字节顺序的标准字节顺序,这个顺序其实就是大端模式;而当时,同时代的 x86 系列主机反而是小端模式。所以就促使产生了如:所以我们这里使用 32 bit 小转大的 htonl() 宏函数来解决这个问题。/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @return int整型 */ int convert(int n ) { // write code here return htonl(n); ); }
2023年07月19日
113 阅读
0 评论
1 点赞
2023-07-13
反向散射理论与ADG902电路实现
后向散射通信技术,是在天线对信号散射的基础上,采用标签后向散射的方式,通过改变发射端标签的反射系数来实现后向散射通信。无线电波在传输过程中,当通过不同介质时,因为介质阻抗的差异性,会产生反射作用,根据介质材料和阻抗的不同,会产生不同的反射量。因此通过调节天线端口的阻抗匹配度,入射的无线电波就可以产生不同反射量,导致入射信号和反射信号的差异性,也就是反射系数 Γ 。具体表示如下:$$\Gamma=\frac$$式中 Z0表示天线端的特征阻抗,一般是 50 Ω,ZL表示标签端口的输入阻抗,Γ表示入射信号振幅和反射信号振幅的复数比。当Γ=0 时,阻抗匹配,入射信号全部传递,无反射信号的产生;Γ = 1时,标签端的输入阻抗为开路,阻抗失配,入射信号被全部反射,产生幅值相同相位相同的反射信号;Γ = −1时,标签端的输入阻抗为短路状态,阻抗失配,入射信号被全部反射,产生幅值相同相位相反的反射信号。因此,通过改变标签的输入阻抗,产生不同的反射系数,就可以控制无线电波的入射和反射,实现有效信号的传递,这也就是后向散射通信的基本原理。为了实现上述后向散射通信,还需要加载天线端口的控制,结构如下图。通过射频开关来控制两个不同的负载阻抗与天线端口的连接,实现阻抗的匹配和失调,完成信号的入射和反射。当开关在负载 Z1和 Z2间转化时,由于负载阻抗的不同,载波信号在天线端的反射比例也不同,因此就产生了不同的调制载波。在实际通信中,为了达到最优传输质量,通常要使反射信号的差异最大,对应完全反射和完全吸收两种形式。对调制后的反射信号进行解调处理后,就可以得到所传递的基带信号,完成后向散射的通信。采用负载 50 Ω 的完全吸收和负载短路的完全反射两种信号传输差异,来实现后向散射的信息传输。使用后向散射开关 ADG 902 来实现基带信号对天线状态的控制,ADG 902 电路结构如图 4.12 所示。将数字基带信号的接入 ADG 902 的 CTRL 端,通过数字基带信号的“0”、“1”来控制 ADG 902 的关断和闭合,RF2 端连接射频天线。通过 ADG 902 中开关的关断,来控制天线对外界调制载波的反射和吸收。当逻辑电平 CTRL=0时,S1关断,S2闭合,此时天线接收端口接地,由式可得,Z=0,T=1,载波信号被完全反射;当逻辑电平 CTRL=1 时,S1 闭合,S2 关断,此时天线接收端匹配,载波被完全吸收。
2023年07月13日
189 阅读
1 评论
1 点赞
Libero SOC Debug教程-片上逻辑分析仪IDENTIFY
FPGA在线调试对于嵌入式系统来说,如单片机,进行硬件级程序调试时,通常采用的是JLink/ST-Link调试器,在线调试的方式来获取程序实时运行的状态,可以观察程序运行流程、各种变量的值、中断的触发情况,还可以设置断点、单步运行,方便快速的发现BUG,解决问题。但是对于FPGA来说,并不是顺序执行的,而是根据每一个Clk并行执行,所以我们不能使用调试器进行单步调试。FPGA调试需要观察内部信号的值,各个信号之间的时序关系,所以使用逻辑分析仪是最好的调试方式了。有些FPGA工程,对外的接口,即输入输出,可能只有几个,但是他们之间的逻辑和时序关系非常复杂,所以内部有几十个中间寄存器,程序下载进去了,发现不是我们想要的效果,怎么办?你可能会说,查代码吧!如果这个工程非常简单,你可能只需要耗费几分钟或者几个小时就可以定位代码的问题所在。但是如果这是一个非常庞大的工程,内部的中间寄存器、信号,几百上千个,各个模块单独软件仿真都正常,整体仿真也正常,就是下载到实际的芯片中运行不正常。你如何进行问题定位?如果再去进行代码审查,这将会消耗非常多的时间。那么如果能在FPGA芯片内部装上一个逻辑分析仪,那不就直接可以看到内部信号的值了,而且还可以看到各个信号之间的时序关系。需求推动技术发展,既然开发者有这个需求,那么FPGA厂商肯定会实现这个功能!下面来一起看一下Microsemi FPGA片上逻辑分析仪的使用方法吧!关于MICROSEMI片上逻辑分析仪几大厂商的片上逻辑分析仪:Xilinx厂商ISE开发环境下的ChipScope工具Altera厂商Quartus开发环境下的SignalTap工具Lattice厂商Diamod开发环境下的Reveal工具对于 FPGA 工程师来说,这些都是很熟悉的名字。和以上几大FPGA厂商一样,Microsemi Libero也支持片上逻辑分析仪工具,只不过不是自己家研发的,使用的是Synospsy公司出品的Identify工具,其实,Libero中的综合器synplify也是Synospsy公司的。根据Synospsy官网的描述:Identify RTL 调试仪,这个调试工具除了支持Microsemi的FPGA产品外,还支持Altera和Xilinx的FPGA产品。FPGA片上逻辑分析仪原理Identify片上逻辑分析仪的原理,是通过在FPGA工程中加入一个IICE逻辑分析仪IP核,这个IP核,由控制器和采集器组成,采集器用于采集信号,控制器用于和JTAG调试器连接,并把数据发送到上位机,IICE内部有RAM空间,用于存储触发位置附近的信号,RAM空间的大小,即采样深度,可以自己调整。FPGA工程中加入IICE核,会占用一定的资源,资源占用的大小取决于:采样深度,采样信号的个数,采样信号的触发方式等。所以综上,FPGA片上逻辑分析仪需要3个组件:片上的IICE逻辑分析仪核、JTAG下载器、上位机。JTAG下载器也就是我们下载程序时使用的FlashPro x下载器,上位机软件也就是Identify工具,这个工具已经在安装Libero SoC时一同安装并注册**了。所以不需要安装其他的工具软件,只需要在已经设计好的FPGA公司中,配置一下IIC逻辑分析仪核就可以了。在已经创建好的Libero工程中,加入IICE逻辑分析仪核,并演示Identify工具的使用。预期效果以Microsemi SmartFusion系列的A2F200M3F芯片为例,其他芯片使用操作方法类似。示例工程功能:led每隔10个clk翻转一次为例,演示identify的使用。identify添加完成之后,把led设置为上升沿触发,会抓取到类似如下的波形。0.准备一个创建好的LIBERO工程这里以LED每隔10个时钟周期翻转为例。HDL文件内容:module led_demo( //inputs input clk, input rst_n, //outputs output reg led ); reg [3:0] cnt; always @ (posedge clk) begin if(!rst_n) cnt <= 0; else if(cnt == 10) /* max=10, 0-10 */ cnt <= 0; else cnt <= cnt + 1; end always @ (posedge clk) begin if(!rst_n) led <= 0; else if(cnt == 10) led <= ~led; end endmodule1.新建IDENTIFY工程,并添加想要监测的信号1.0 先运行Synthesize 1.1 在Synthesize上右键,选择Open Interactively1.2 在Synthesis上右键新建一个Identify工程1.3 输入新建的identify工程的名称和保存路径,选择默认的就行。1.4 在新建的identify工程上右键选择identify instrumentor1.5 在HDL文件中选择要监测的信号和采样时钟,采样时钟选择Sample Clock,作为触发的信号选择Trigger Only,要监测的信号选择Sample Only,也可以选择Sample and Trigger,这样会占用更多的资源。设置完成的信号会有标注 sample clock 表示采样时钟,所有在 IICE 中添加的信号都会在 sample clock 的边沿进行采样,设为 sample clock 的信号前会出现一个时钟状的图标。设置为 sample 和 trigger 的信号都将作为被采样信号,区别在于 sample 信号只能被采样,而 trigger 信号可以作为触发采集的条件,当然你可以把一个信号同时设置为 sample 和 trigger 。1.6 设置采样深度,选择Instrumentor->IICE采样深度最大支持1048576输入采样深度,数值越大,采样时间越长,相应的FPGA资源占用也越多。1.7 选择Run->Run或者直接点击主界面的Run按钮1.8 编译完成之后,保存退出。2.管脚分配,编译下载2.1 和正常流程一样,管脚分配,编译下载。可以看到JTAG部分的管脚已经被IICE逻辑分析仪核使用了2.2 在Identify Debug Design上右键,选择Open Interactively,打开identify工具3.设置触发类型3.1 选择要触发的信号,和触发类型,这里我选择的是led,上升沿触发。3.2 连接FlashPro下载器,点击小人图标,启动抓取,满足触发条件自动停止。INFO: run -iice IICEINFO: Info: Attempting to connect to: usb Info: Type: FlashPro4 Info: ID: 08152 Info: Connection: usb2.0 Info: Revision: UndefRevINFO: Checking communication with the Microsemi_BuiltinJTAG cable and the hardwareINFO: The hardware is responding correctlyINFO: Auto-detecting the device chainINFO: Device at chain position 1 is "A2F200M3F"INFO: IICE 'IICE' configured, waiting for triggerINFO: IICE 'IICE' Trigger detected, downloading samplesINFO: notify -notifyINFO: waveform viewerINFO: waveform viewerINFO: write vcd -iice IICE -comment -gtkwave -noequiv IICE.vcdD:/identify_demo/synthesis$ "/}3.3 右侧黄色的显示就是触发瞬间时信号的值。右键可以改变数据格式。3.4 选择Debugger preferences可以设置采样时钟的周期,用于后面波形的时间测量3.5 设置采样时钟的周期3.6 点击波形按钮,在GTKWave中打开抓取到的波形。3.7 可以按住左键拖动测量时间差3.8 还可以给每个通道设置不同的颜色,和显示方式。4.IICE逻辑分析仪核资源占用IICE逻辑分析仪核占用的主要是逻辑资源和RAM资源,可以看到资源占用还是很多的。参考:https://zhuanlan.zhihu.com/p/88314552https://www.synopsys.com/zh-cn/implementation-and-signoff/fpga-based-design/identify-rtl-debugger.htmlhttp://training.eeworld.com.cn/video/1059https://www.microsemi.com/document-portal/doc_view/132760-synopsys-identify-me-h-2013-03m-sp1-user-guide
2023年07月13日
775 阅读
0 评论
0 点赞
1
2
3
...
25