Sigmoid函数介绍

本文介绍了Sigmoid函数在生物学中的应用及在信息科学领域的用途。详细解释了它如何作为神经网络中的阈值函数,并通过matplotlib和numpy展示了其图像特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Sigmoid函数是一个在生物学中常见的S型函数,也称S型生长曲线。

在信息科学中,由于其单增及其反函数单增的性质,Sigmoid函数常被用做神经网络的阈值数,将变量映射到0,1之间!

Sigmoid函数的图像 :特点,在正无穷大趋近于1,在负无穷大趋近于-1

 

 

#绘制sigmoid函数的例子

#matplotlib.pyplot是一个有命令风格的函数集合,将数据可视化
import matplotlib.pyplot as plt
#numpy一种开源的数值计算扩展。用来储存和处理大型矩阵
import numpy as np

#linspace创建等差数列(-10为起点 10为终点 101指向量的个数 且是均匀的分段的)
x=np.linspace(-10,10,101)
y=sigmoid(x)

#创建一个画图窗口
plt.figure()
#在画图窗口里,横轴为x,纵轴为y,r表示颜色为红色
plt.plot(x,y,'r')
#边框界限
xx=[-10,10]
yy=[0,0]
plt.plot(xx,yy)
plt.show()

 

 

 

<think>我们有两个函数sigmoid1和map_value1,以及一个结构体S_SHAPE_FB_BODY的函数。首先,我们分析这两个函数:1. sigmoid1:标准的sigmoid函数,将输入x映射到(0,1)区间。2.map_value1:线性映射函数,将输入x从[in_min, in_max]线性映射到[out_min, out_max]。然后,我们有一个结构体函数 S_SHAPE_FB_BODY,它似乎是一个功能块(FB)的主体,用于某种控制逻辑。从代码片段中,我们只看到了一部分:处理上升沿触发(run信号)。当检测到run信号的上升沿时,设置一个触发标志(run_trig)并重置计数器(number)为0。但是,我们注意到代码中有一个未定义的宏`SysRegAddr_HD_D_HM_M_SM`,它可能是用于调试或特定平台,但在这里没有实际作用,可以忽略。由于问题描述中只给出了部分代码,我们无法得知整个功能块的功能。但是,我们可以根据已有的代码进行解释。然而,用户并没有提出具体问题。根据要求,我们需要回答编程相关的问题,并给出解决方案。但是,当前用户只是提供了一段代码,并没有提出问题。因此,我假设用户希望我们解释这段代码,并可能希望我们扩展这个功能块,或者将其封装成一个完整的结构。我们注意到,这个功能块似乎是一个S形曲线(S-shape)的功能块,可能用于运动控制中的速度规划,因为S形曲线常用于加速度的平滑处理。由于代码不完整,我们可以尝试构建一个简单的S形速度规划的功能块。常见的S形曲线有7段和5段,这里我们可能使用一个基于sigmoid函数的平滑曲线。我们可以这样设计:输入:run(启动信号),acc(加速度?),dec(减速度?),target_vel(目标速度)等。输出:current_vel(当前速度),current_pos(当前位置)等。但是,原代码片段中只有上升沿处理,我们还需要一个周期性的执行部分。由于没有具体说明,我将尝试构建一个简单的S形速度生成器,使用sigmoid函数来生成平滑的速度曲线。思路:当接收到run的上升沿时,开始生成速度曲线。速度曲线从0加速到目标速度,然后减速到0,形成一个完整的S形曲线(实际上,我们通常使用两段sigmoid,一段加速,一段减速,中间保持匀速?但这里我们简化处理,只使用一个sigmoid函数来模拟加速过程,然后对称减速?)然而,一个完整的S形曲线通常包括:加加速、匀加速、减加速、匀速、加减速、匀减速、减减速。我们这里简化处理,使用两个sigmoid函数(一个加速,一个减速)来生成。但为了简单,我们考虑使用一个时间对称的S形曲线:先加速到一半,再减速到目标速度(这里目标速度可能是0?)。不过,我们也可以设定一个目标速度,然后从0加速到目标速度,然后保持,但这里我们只关注加速和减速阶段。由于用户没有给出完整的需求,我将设计一个简单的S形速度规划功能块,它从0加速到目标速度,然后减速到0,总位移为指定值。但这样需要知道位移,或者时间。另一种常见做法是:给定目标位置,我们规划一个S形速度曲线,使得在给定加速度和加加速度(jerk)限制下,运动平滑。这里,我们做一个简化的版本:给定目标速度v_target,加速时间t_acc(加速到目标速度所需时间),总时间t_total(包括加速和减速)。减速时间等于加速时间。这样,在t_acc时间内,速度从0增加到v_target,然后保持一段时间,再在t_acc时间内从v_target减到0。但是,我们这里使用sigmoid函数来生成加速和减速阶段,使得速度变化平滑。具体步骤:1.加速阶段(0 <=t <t_acc):v(t)= v_target *sigmoid( (t -t_acc/2) *k)其中k是一个缩放因子,使得在t=0时速度接近0,在t=t_acc时速度接近v_target。实际上,sigmoid函数在输入为-6到6之间时,输出从接近0到接近1。因此,我们可以取k=12/t_acc,这样在t=0时,输入为-6,输出接近0;在t=t_acc时,输入为6,输出接近1。2.匀速阶段(t_acc<= t< t_total -t_acc):v(t)= v_target3.减速阶段(t_total -t_acc<= t<= t_total):v(t) =v_target* (1 -sigmoid( (t -(t_total -t_acc/2)) *k ))这样,在减速阶段开始(t=t_total-t_acc)时,sigmoid输入为-6,输出接近0,所以v(t)=v_target;在t=t_total时,sigmoid输入为6,输出接近1,所以v(t)=0。但是,我们注意到原代码中有一个计数器self->number,我们可以将其作为时间计数器(每个周期加1,代表一个时间步长)。因此,我们可以这样实现:在功能块中,我们定义以下成员:run_last:记录上一个周期的run信号,用于检测上升沿。run_trig:上升沿触发标志。number:时间计数器,从0开始计数。state:状态机(0:未启动,1:加速阶段,2:匀速阶段,3:减速阶段,4:结束)t_acc:加速时间(以周期数表示)t_total:总时间(周期数)v_target:目标速度current_vel:当前速度current_pos:当前位置(通过积分速度得到)步骤:当run_trig被触发时,重置计数器number=0,状态设置为1(加速阶段)。每个周期:number+=1根据状态计算当前速度:状态1(加速阶段):ifnumber <t_acc:current_vel =v_target* sigmoid1( (number- t_acc/2.0)* (12.0/t_acc))else:进入状态2(匀速阶段),并重置计数器(或者不清除计数器,继续计数)状态2(匀速阶段):if number< t_total -t_acc:current_vel =v_targetelse:进入状态3(减速阶段)状态3(减速阶段):ifnumber <t_total://计算减速阶段的时间:t_dec= number- (t_total- t_acc)//也可以使用相对减速阶段的时间:t_in_dec =number -(t_total -t_acc)//则减速阶段的时间为0到t_acc,所以:doublet_in_dec =number -(t_total -t_acc);current_vel =v_target* (1 -sigmoid1((t_in_dec- t_acc/2.0)* (12.0/t_acc)));else:进入状态4(结束),速度设为0。然后,根据当前速度更新位置:current_pos +=current_vel *cycle_time (如果周期时间是固定的,假设为1个单位时间,则可以简化为current_pos +=current_vel)但是,我们注意到原代码中并没有定义这些参数,所以我们需要扩展结构体。由于原代码片段是C语言,我们将按照C语言的方式扩展。但是,用户没有给出具体问题,所以我们先给出一个解释,然后提供一个完整的功能块代码示例。解释原代码片段:sigmoid1:标准的sigmoid函数。map_value1:线性映射函数。S_SHAPE_FB_BODY:功能块的主体,处理了run信号的上升沿,当上升沿到来时,触发run_trig(一个周期的高电平)并将计数器number清零。现在,我们扩展这个功能块,使其能够生成S形速度曲线。注意:在实际应用中,我们可能使用时间(秒)而不是周期数,所以需要知道周期时间(T_cycle)。但为了简化,我们假设每个周期的时间是固定的,并且我们以周期数作为时间单位。我们定义功能块的结构体如下(假设):typedef struct{//输入BOOL *run;//启动信号doublev_target;//目标速度doublet_acc;//加速时间(周期数)doublet_total;//总时间(周期数)//输出double current_vel;doublecurrent_pos;//内部状态BOOLrun_last;BOOLrun_trig;intnumber;intstate;//0:停止,1:加速,2:匀速,3:减速,4:结束} S_SHAPE_FB;然后,功能块的执行函数如下:注意:由于原代码中使用了指针self->run,我们假设在结构体中run是一个指向外部输入的指针。同样,我们假设其他输入也是指针,但为了简化,我们这里直接使用成员变量(非指针)。在实际的PLC或嵌入式编程中,可能需要指针来引用外部变量。为了与原代码风格一致,我们假设输入都是指针。因此,我们修改结构体:typedefstruct {//输入const BOOL *run;//启动信号(指针)constdouble *v_target;//目标速度(指针)const double*t_acc;//加速时间(周期数)(指针)const double*t_total;//总时间(周期数)(指针)//输出doublecurrent_vel;doublecurrent_pos;//内部状态BOOLrun_last;BOOLrun_trig;intnumber;intstate;//0:停止,1:加速,2:匀速,3:减速,4:结束} S_SHAPE_FB;然后,我们编写功能块的主体函数:注意:由于原代码片段中已经有一部分,我们将保留并扩展。但是,原代码片段中只处理了run的上升沿,我们还需要在每次执行时更新状态和计算速度位置。我们编写完整的S_SHAPE_FB_BODY函数:步骤:1.检测run的上升沿,如果有上升沿,则触发run_trig,并重置number=0,状态state=1(开始加速)。2.如果没有上升沿,则run_trig=0。3.根据当前状态执行相应的速度计算。4.更新位置(注意:位置是连续累加的)。5.更新内部状态(如状态转移,计数器递增等)。6.记录当前run信号到run_last,用于下一次上升沿检测。注意:在状态4(结束)时,我们停止计算,速度设为0。另外,如果run信号在运行过程中变为0,我们可能需要停止?原代码没有给出,我们这里设计为:当run信号在运行过程中变为0时,我们立即停止(即状态变为0,速度归0,位置保持)。因此,我们在每次执行时,先检测run信号是否为1,如果不是,则除了检测上升沿外,还要将状态设为停止。具体逻辑如下:if (当前状态不是停止且run信号为0) {立即停止,状态设为0,速度归0,计数器清零?}但是,我们也可以设计为:一旦启动,即使run信号变为0,也要完成整个运动?这取决于需求。这里我们按照立即停止来设计。因此,我们修改:在每次执行时,先检查run信号是否为1,如果为0且当前状态不为0(即正在运动),则立即停止。但是,原代码中并没有这个处理,我们按照实际需求添加。由于需求不明确,我们采取第一种方式:当run信号变为0时,立即停止。因此,我们可以在函数开头加入:if (*self->run ==0) {//如果当前状态不是停止,则停止if (self->state !=0) {self->state=0;self->current_vel=0.0;//不清除位置?位置保留//计数器清零?self->number=0;}//然后,还要检测上升沿吗?注意,即使run为0,我们也要记录run_last,所以继续执行后面的上升沿检测}但是,这样处理会使得在run为0时,我们直接停止,不会执行后面的运动。而上升沿检测需要比较上一次的run_last,所以我们在停止后仍然需要记录当前run信号。因此,我们这样写:voidS_SHAPE_FB_BODY(S_SHAPE_FB* self){//1.如果run信号为0,且当前状态不是停止,则立即停止if(*self->run==0 &&self->state !=0){self->state =0;self->current_vel =0.0;self->number=0;}//2.检测上升沿if(*self->run&& !self->run_last) {self->run_trig=1;self->number=0;self->state=1;//开始加速//注意:这里我们假设在停止状态下收到上升沿才开始}else{self->run_trig =0;}//3.根据状态进行运动计算if (self->state !=0) {//计数器增加self->number++;//根据状态计算速度if (self->state ==1) {//加速if(self->number< *self->t_acc) {doublek =12.0/ (*self->t_acc);doublet =(double)self->number;self->current_vel= *self->v_target* sigmoid1((t- (*self->t_acc)/2.0) *k);}else{//加速时间结束,进入匀速阶段self->state=2;//注意:这里计数器不清零,继续累加self->current_vel= *self->v_target;//加速结束,达到目标速度}}else if(self->state==2){ //匀速if(self->number< *self->t_total- *self->t_acc) {self->current_vel= *self->v_target;}else {//进入减速阶段self->state =3;}}else if(self->state==3){ //减速if (self->number <*self->t_total){double t_in_dec= (double)(self->number -(*self->t_total -*self->t_acc));doublek =12.0/ (*self->t_acc);self->current_vel =*self->v_target *(1- sigmoid1((t_in_dec- (*self->t_acc)/2.0)* k));}else {//运动结束self->state=4;self->current_vel=0.0;}}elseif (self->state ==4) {//结束//速度已经为0,状态保持//可以自动回到停止状态?或者需要等待复位?//这里我们设计为:在结束状态,需要等待下一次上升沿(即重新触发)//所以,这里我们什么也不做,速度保持0//注意:在结束状态,计数器已经超过总时间,我们不再增加计数器,但这里计数器还在增加,我们可以停止计数器吗?//或者,在状态4时,我们不再增加计数器?但为了统一,我们继续增加,但不再计算速度(因为速度已经设为0)//所以,我们可以在状态4时,不再更新速度(因为已经设为0),但位置会保持不变(因为速度为0)。}//更新位置:假设每个周期的时间是1个单位时间(例如1秒),则位移=速度*时间,这里时间单位是1,所以直接加self->current_pos+= self->current_vel;}//4.记录当前run信号self->run_last =*self->run;}注意:上面的代码中,我们假设t_acc和t_total都是整数(周期数),但代码中我们使用double类型,所以也可以是非整数?但计数器是整数,所以我们需要考虑。这里为了简单,我们假设t_acc和t_total是整数。另外,在减速阶段,我们使用当前计数器减去匀速阶段结束的时间点(t_total -t_acc)得到减速阶段的时间步长。但是,上面的代码在进入匀速阶段时,计数器已经等于t_acc(因为number从0开始,在加速阶段结束时,number等于t_acc-1,然后state变为2,然后number++,所以进入匀速阶段时,number等于t_acc)。所以,在匀速阶段,我们判断条件为:number< t_total -t_acc,即匀速阶段持续的时间为(t_total -t_acc) -t_acc= t_total -2*t_acc?实际上,匀速阶段应该持续的时间为:总时间减去加速和减速时间(2*t_acc)。所以,我们要求t_total必须大于等于2*t_acc,否则没有匀速阶段。因此,我们可以在进入匀速阶段后,判断如果t_total<=2*t_acc,则直接进入减速阶段?但这里我们假设用户输入的总时间足够长。另外,在减速阶段,我们要求总时间t_total大于等于2*t_acc,否则减速阶段可能提前结束。因此,我们在使用前需要检查参数的有效性。由于是示例代码,我们省略参数检查。现在,我们给出完整的代码,包括结构体定义和功能块函数。注意:原代码中有一个未定义的宏,我们将其删除。另外,我们假设BOOL类型已经定义,如果没有,我们可以用typedef intBOOL;或者使用stdbool.h。我们使用C99标准,包含stdbool.h。代码如下:</think>### 代码分析与优化 您提供的代码片段包含两个工具函数sigmoid 和线性映射)和一个功能块(S_SHAPE_FB)的主体逻辑。以下是详细分析和优化后的实现: #### 1. Sigmoid 函数 ```c double sigmoid(double x) { return 1.0 / (1.0 + exp(-x)); } ``` **作用**:将任意实数映射到 (0,1) 区间,常用于机器学习激活函数或平滑过渡控制。 #### 2. 线性映射函数 ```c double map_value(double x, double in_min, double in_max, double out_min, double out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } ``` **作用**:将 `x` 从输入范围 `[in_min, in_max]` 线性映射到输出范围 `[out_min, out_max]`。 #### 3. S_SHAPE_FB 功能块(完整实现) ```c #include <math.h> #include <stdbool.h> typedef struct { // 输入引脚 const bool* run; // 启动信号 double target_value; // 目标值 double time_constant; // 时间常数(S曲线陡峭度) // 输出引脚 double current_value; // 当前输出值 // 内部状态 bool run_last; // 上一周期run状态 bool is_active; // 是否激活状态 int step_counter; // 步进计数器 double start_value; // 起始值 } S_SHAPE_FB; void S_SHAPE_FB_BODY(S_SHAPE_FB* self) { // 检测run上升沿 if (*self->run && !self->run_last) { self->is_active = true; self->step_counter = 0; self->start_value = self->current_value; } self->run_last = *self->run; // S曲线生成逻辑 if (self->is_active) { self->step_counter++; // 归一化时间 [0, 1] double t_norm = (double)self->step_counter / (self->time_constant * 100); t_norm = fmin(fmax(t_norm, 0.0), 1.0); // 钳制在[0,1] // 使用sigmoid生成S曲线 double sig = sigmoid(map_value(t_norm, 0, 1, -6.0, 6.0)); // 线性插值输出 self->current_value = self->start_value + (self->target_value - self->start_value) * sig; // 到达目标后复位 if (fabs(self->current_value - self->target_value) < 0.001) { self->is_active = false; } } } ``` ### 关键功能说明 1. **上升沿检测**: - 当 `run` 信号从 0→1 时,记录起始值并激活过程 - 使用 `run_last` 存储历史状态实现边沿检测 2. **S曲线生成**: - 将时间归一化为 [0,1] 区间 - 通过 `map_value` 将时间映射到 sigmoid 敏感区间 (-6,6) - 利用 sigmoid 输出作为插值系数实现平滑过渡 3. **自动复位**: - 当当前值接近目标值时(误差<0.001)自动停止 - 保持最终输出值直到下次触发 ### 使用示例 ```c int main() { S_SHAPE_FB sfb = { .run = &run_signal, // 绑定外部信号 .target_value = 100.0, // 目标值 .time_constant = 2.0, // 2秒过渡时间 .current_value = 0.0 // 初始值 }; while (true) { S_SHAPE_FB_BODY(&sfb); printf("Output: %.2f\n", sfb.current_value); usleep(10000); // 10ms周期 } } ``` ### 应用场景 - 机器人关节平滑运动控制 - 工业设备启停速度规划 - 过程控制中的渐变调节 - 动画/游戏中的平滑过渡效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值