这几天用神经网络做一个数据逼近,想用来在嵌入式系统上取代查表,对网络规模有限制,最多7层,每层不超过64个神经元。用了一堆激活函数,效果都不好,于是又从哲(直)学(觉)的角度想了想,神级网络到底是个啥?
我曾经在一个为什么relu效果比sigmoid好的问题下回答说,神经网络就是个拟合器。抛开神经网络,传统上的拟合都是分解出一组基函数,再加权求和,泰勒分解的基函数是幂函数,傅立叶分解的基函数是三角函数,拉氏变换则是复平面指数函数,这些基函数都是作用域在全定义域上的连续函数,他们的特点就是每个点上几乎都是所有基函数的叠加,所以相互的耦合特别严重,牵一发而动全身,这在工程上是很不好处理的。真正工程上实用的是什么呢?实际上都是采用分段拟合的方法,样条曲线是分段三次曲线,保证导数连续,有限元则更干脆,直接分段一次函数。他们的特点就是基函数互不重叠,只管自己这一段凑活上就行了,彼此井水不犯河水,所以实现更简单。
RELU函数就有这个特点,小于0的部分直接没了,不会干扰其它人,所以是个半分段拟合,效果就比全域的sigmoid好。但是呢,RELU大于0的部分还是会影响别人的,所以只能算半个分段拟合。此外还有个负段梯度消失的问题,所以又引入了一堆负段非0梯度的变体,不过总的来说效果都差不多吧。
于是我就想,能不能要做就做干脆点,把半拉子分段搞成完全分段的,也就是只在一段范围内有值,其它都为0的激活函数?于是我试了下这个最简单的形式:
nn.LeakyReLU(1e-6)( 1- x**2)
也就是输入在(-1,1)之间有正值,其它区间都为0(为了防止梯度消失,加了个LeakyReLU)。这下就成了一个完全分段的函数了。
试了一下,效果还不错,但是捏,显然这个函数,与其叫激活函数,不如叫灭活函数,多用几次大部分梯度就消失了,所以太极端了,于是我又改了一下,变成这个样子
w=linear(x)
w=nn.leakyRelu(1-w**2)
out=w*x
linear是一个全连接层,先把输入做一次全连接变换得到权重,再对权重激活(灭活),这样就好多了,多留了几个活口,而且实际上从二次分段函数变成了三次曲线,更贴近与工程上的样条曲线。
但是层数多了还是会梯度消失,所以最好是加上残差,但这个函数直接加残差,则由于w总是在(0,1)之间,加上残差总是正偏,效果不好,所以我又改成这样
w=linear1(x)
w=1-w**2
w=linear2(w)
out=w*x+x
用了两个全连接层,把灭活的权重再组合一次,得到范围更大的比例,再乘加输入,形成一个带残差的结果,这样就基本消除了梯度消失的问题了。
值得注意的是我为了减少计算量,对这两个全连接层采用了按比例降维——升维的操作,也就是w的维度先降个几倍,然后再升到和输入一样的维度。因为我觉得既然目标是灭活,所以大部分w都应该是0,也就没必要保持和输入一样的维度。我采用的降维比例是4,看起来效果没什么区别,大家可以自行测试。
总之我在我的数据拟合应用上测试了一下这个函数,效果比RELU好的太多了,各位可以试试,说不定有惊喜呢。