激活函数
神经网络其实可以简单理解为将各种变量作为输入,然后对每个输入进行加权,但是对于我们想要的结果如果是回归还好,如果是分类,我们想得到的其实就是个概率,但是有时候一些输入是一种异常值(可以对输入进行预处理),可能会导致输出超出我们想要的范围,因此需要激活函数将输出约束到一定的范围
一下简单记录一下darknet中的激活函数和他们的梯度(倒数),在darknet中是将他们作为静态的内敛函数,这样节省了调用开销
1.linear_activate
线性激活函数,什么也不做,输入等于输出,在darknet中
static inline float linear_activate(float x)
{
return x;
}
函数图像
2.logistic_activate
我们叫做sigmoid激活函数,这个函数的图像像是s形,它将输入压缩到[0,1]之间,如果我们想要的结果就是概率的话,这个激活函数就非常适合,他的有点就是连续,方便求导,但是计算量大
static inline float logistic_activate(float x)
{
return 1./(1. + exp(-x));
}
函数图像
3.loggy_activate
这个激活函数将结果压缩到了-1,1,如下图蓝色的线
函数方程为
static inline float loggy_activate(float x)
{
return 2./(1. + exp(-x)) - 1;
}
4.relu_activate
根据图像就可以看出来,当输入小于0的时候输出0,当x>0的时候原样输出
优点:
- 计算量小;
- 激活函数导数维持在1,可以有效缓解梯度消失和梯度爆炸问题;
- 使用Relu会使部分神经元为0,这样就造成了网络的稀疏性,并且减少了参数之间的相互依赖关系,缓解了过拟合问题的发生。
缺点:输入激活函数值为负数的时候,会使得输出为0,那么这个神经元在后面的训练迭代的梯度就永远是0了(由反向传播公式推导可得),参数w得不到更新,也就是这个神经元死掉了。这种情况在你将学习率设得较大时(网络训练刚开始时)很容易发生(波浪线一不小心就拐到负数区域了,然后就拐不回来了)。
解决办法:一些对Relu的改进,如ELU、PRelu、Leaky ReLU等,给负数区域一个很小的输出,不让其置0,从某种程度上避免了使部分神经元死掉的问题。
static inline float relu_activate(float x)
{
return x*(x>0);
}
函数图像
5.elu_activate
上面的relu激活函数在x<0时候,输出0,那么导数就是0,出现梯度消失,为了解决这个现象,elu激活函数在x<0的时候做了指数处理,可以让梯度不为零,继续学习权重
在x>0:输入==输出
在x<0:做指数处理
右侧线性部分使得elu能够缓解梯度消失,而左侧软饱部分能够让ELU对输入变化或噪声更鲁棒。elu的输出均值接近于零
static inline float elu_activate(float x)
{
return (x >= 0)*x + (x < 0)*(exp(x)-1);
}
函数图像
6.selu_activate
和上面的elu相比,对输入做了修正,1.0507 是修正系数,如下面蓝色的图像,selu和elu缺点就是在x<0的时候计算量大
static inline float selu_activate(float x)
{
return (x >= 0)*1.0507*x + (x < 0)*1.0507*1.6732*(exp(x)-1);
}
函数图像
7.relie_activate
relie激活函数在x<0的时候添加了要给小的梯度,解决x<0的时候梯度为0的情况,
在x>0:输入==输出
在x<0:0.01x
static inline float relie_activate(float x)
{
return (x>0) ? x : .01*x;
}
8.ramp_activate
ramp和relie类似,就是在斜率上做了修正
static inline float ramp_activate(float x)
{
return x*(x>0)+.1*x;
}
函数图像:青色
9.leaky_activate
可以看出来leaky和relie就是在x<0的时候修正系数不同
static inline float leaky_activate(float x)
{
return (x>0) ? x : .1*x;
}
函数图像:青色
10.tanh_activate
tanh是双曲线函数,tanh 是一个双曲正切函数。tanh 函数和 sigmoid 函数的曲线相对相似。但是它比 sigmoid 函数更有一些优势。
首先,当输入较大或较小时,输出几乎是平滑的并且梯度较小,这不利于权重更新。二者的区别在于输出间隔,tanh 的输出间隔为 1,并且整个函数以 0 为中心,比 sigmoid 函数更好;
在 tanh 图中,负输入将被强映射为负,而零输入被映射为接近零。
注意:在一般的二元分类问题中,tanh 函数用于隐藏层,而 sigmoid 函数用于输出层,但这并不是固定的,需要根据特定问题进行调整。
static inline float tanh_activate(float x)
{
return (exp(2*x)-1)/(exp(2*x)+1);
}
static inline float tanh_activate(float x)
{
return exp(*x)-exp(*-x)/exp(*x)+exp(*-x);
}
函数图像
红色:sigmoid
青色:对sigmoid做了修正
蓝色:tanh
11.plse_activate
使用分段函数
static inline float plse_activate(float x)
{
if(x < -4) return .01 * (x + 4);
if(x > 4) return .01 * (x - 4) + 1;
return .125*x + .5;
}
12.lhtan_activate
比plse减小了梯度
static inline float lhtan_activate(float x)
{
if(x < 0) return .001*x;
if(x > 1) return .001*(x-1) + 1;
return x;
}
求导函数
对于求导,也就是梯度,对于线性函数,这个很简单,我们这里主要说明非线性函数,并画出图像