6 折与曲的相会——激活函数🍈
1 前言
在上一节,我们实现了一个“自适应线性单元”,不断地将一个一次函数的输入和输出“喂”给它,它就可以自动地找到一次函数y = w x + b 中合适的参数值w和b。计算图通过前向传播和反向传播,初步展现了它的神奇之处。
但在实际遇到的问题中,输入与输出之间往往并不是简单的线性关系,它们之间的函数关系可能是二次的、指数、甚至分段的。此时”自适应线性单元“就不足以满足我们的需求了。而”激活函数“,将为计算图带来一种拟合这些非线性函数关系的能力。
同时为了得到对于激活函数更加清晰和形象化的认知,本节我们还将使用matplotlib对拟合过程进行一些可视化的展现。
本节任务:在计算图中加入激活函数relu,拟合二次函数 y=x^2y
在区间[0,2]的一小段曲线。
2 激活函数
关于”激活函数“这个名称(非专业解释),首先我们可以看阶跃函数。当输入超过0这个阈值时,输出就从0跳到了1,0是一个非激活的状态,而1是一个激活的状态。这个和生物领域中神经元间的突触有一定相似性,当突触间的兴奋性神经元递质超过某个阈值后,下一个神经元才会进入兴奋状态继续传递信号。
2.1 Relu
人们发明了许多各式各样的激活函数,它们有着不同的特点,而Relu是其中比较常用的一种。Relu是一个简单的分段函数,它的核心思想是通过多段折线来贴近曲线,折线段越多、越短,拟合效果就越好,理论上使用relu几乎可以较好地任何曲线。
Relu节点的实现:
# ourdl/ops/ops.py class Relu(Op): def compute(self): assert len(self.parents) == 1 self.value = self.parents[0].value if self.parents[0].value >= 0 else 0 def get_parent_grad(self, parent): return 1. if self.parents[0].value > 0 else 0 # 发现relu的导函数就是step @staticmethod def relu(x: float): '''静态方法 --> 在计算图之外使用relu''' return x if x >= 0. else 0.
在前向传播的过程中,它接受一个父节点的输入,并产生一个输出。我们还使用装饰器@staticmethod
,实现了一个静态方法relu(x)
,这样我们也可以在计算图之外直接调用relu函数了,例如可以在使用matplotlib绘制函数图像时用到。
2.3 LeakyRelu
和加法节点、乘法节点等节点一样,激活函数也是计算图中的一个运算节点,需要在该节点类中实现对应的get_parent_grad()
方法对父节点进行求导。Relu函数在输入小于0时函数值都是0,对应的导数也是0,这种情况下参数就不会进行更新了。
人们提出了一种对Relu函数的修正方案,那就是LeakyRelu。在输入大于等于0的部分函数值不变,仍然是x;但是在输入小于0的部分取0.1 x 0.1x0.1x,这样在反向传播的过程中,节点的输入小于0时,虽然导数只有0.1,但并没有直接消失,参数仍然可以进行更新。(这里0.1是一个”超参数“,也可以取其它值)
在我的一些尝试中,使用Relu函数时训练过程会卡住一直无法拟合,但LeakyRelu可以一定程度上缓解问题,仍然可以拟合只是比较慢。
LeakyRelu节点的实现:
# ourdl/ops/ops.py class LeakyRelu(Op): '''消除了relu中导数为0的情况''' def compute(self): assert len(self.parents) == 1 t = self.parents[0].value self.value = t if t >= 0 else t * 0.1 def get_parent_grad(self, parent): return 1. if self.parents[0].value > 0 else 0.1 # 发现relu的导函数就是step @staticmethod def relu(x: float): '''静态方法 --> 在计算图之外使用leakyrelu''' return x if x >= 0. else x * 0.1
超参数”0.1"直接写死在代码中了,因为它通常并不需要改变。
3 拟合曲线的尝试
3.1 设计计算图
这个计算图中明确地画出了所有的节点,看起来有一些复杂。从整体看,计算图包含了三次变换:
输 入 − − > 线 性 变 换 − − > 激 活 函 数 − − > 线 性 变 换 − − > 输 出
上述三次变换各自的意义是什么?计算图为什么设计成这个样子?这都是很重要的问题。
其实在图一中大致就能得到答案。
第一次变换,产生两条不同的直线,它们有着不同的斜率,更重要的是:它们与x轴有着不同的交点。
第二次变换,使用激活函数relu(图一中是LeakyRelu),两条直线都在与x轴的交点处折断,得到两条折线。
第三次变换,两条折线线性叠加,由于它们折断点不同,故得到是一个三段折线。
而我所希望的,就是利用三段的折线去尽可能地贴合二次函数的曲线。
【一起撸个深度学习框架】6 折与曲的相会——激活函数(2):https://developer.aliyun.com/article/1407204?spm=a2c6h.13148508.setting.25.79f64f0ecKMDuK