非线性激活函数

1 为什么需要非线性激活函数?

如果使用线性激活函数,那么这个模型的输出不过是你输入特征x的线性组合。神经网络只是把输入线性组合再输出。
所以即使你有很多个隐含层,但是你如果使用线性激活函数或者不用激活函数,一直在做的只是计算线性激活函数,所以还不如直接去掉全部隐藏层。所以除非引入非线性,那么无法计算更有趣的函数。
只有一个地方可以使用线性激活函数,那就是回归问题。或者在输出层用也是可以的。

2 四个非线性激活函数

四个激活函数.png

Sigmoid(左上角):
现在吴恩达几乎从来不用sigmoid激活函数了,但是吴恩达会用sigmoid的一个例外场合是进行二元分类时。
缺点:
1、Sigmoid容易饱和,并且当输入非常大或者非常小的时候,神经元的梯度就接近于0了,从图中可以看出梯度的趋势。这就使得我们在反向传播算法中反向传播接近于0的梯度,导致最终权重基本没什么更新,我们就无法递归地学习到输入数据了。另外,你需要尤其注意参数的初始值来尽量避免saturation的情况。如果你的初始值很大的话,大部分神经元可能都会处在saturation的状态而把gradient kill掉,这会导致网络变的很难学习。容易饱和这个问题叫做 “梯度饱和” ,也可以叫 “梯度弥散” 。
2、Sigmoid 的输出不是0均值的,这是我们不希望的,因为这会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响:假设后层神经元的输入都为正(e.g. x>0 elementwise in ),那么在反向传播的过程中对w求梯度要么都为正,要么都为负(取决于整个表达式 f 的梯度)。这可能会在权重的梯度更新中引入不受欢迎的zig-zagging动态。导致有一种捆绑的效果,使得收敛缓慢。当然了,如果你是按batch去训练,那么每个batch可能得到不同的符号(正或负),那么相加一下这个问题还是可以缓解。因此,非0均值这个问题虽然会产生一些不好的影响,不过跟上面提到的 kill gradients 问题相比还是要好很多的。

Relu(左下角):
Relu:a = max(0,z),但是当z = 0时,导数是没有定义的,但如果编程实现,你得到的z刚好等于0.000000000000的概率很低,所以不必担心。
Relu已经变成激活函数的默认选择了,当你不知道因隐层到底该用哪个激活函数时,就可以用relu。虽然有人也会用tanh。Relu的一个缺点:当z为负时,导数等于0。但在实际中却没有什么问题。虽然对于z的一半范围来说,relu的斜率为0,但在实践中,有足够多的隐藏单元令z大于0,所以对于大多数训练样本来说还是很快的。

3 实战一 —— 输入tensor观察非线性激活函数的作用

import torch
from torch import nn
from torch.nn import ReLU,Sigmoid
input = torch.tensor([[1, -0.5],
                      [-1, 3]])
input = torch.reshape(input,(-1, 1, 2, 2))
print((input.shape))
class Linyu(nn.Module):
    def __init__(self):
        super(Linyu, self).__init__()
        self.relu1 = ReLU()

    def forward(self,input):
        output = self.relu1(input)
        return output

linyu = Linyu()
output = linyu(input)
print(output)

输出:

torch.Size([1, 1, 2, 2])
tensor([[[[1., 0.],
          [0., 3.]]]])

实战二 —— 非线性激活函数在图像处理中的使用

import torch
from torch import nn
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torch.nn import ReLU,Sigmoid

dataset = torchvision.datasets.CIFAR10("../dataset",train=False,download=True,
                                       transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset,batch_size=64,drop_last=True)
class Linyu(nn.Module):
    def __init__(self):
        super(Linyu, self).__init__()
        self.relu1 = ReLU()
        self.sigmoid1 = Sigmoid()

    def forward(self,input):
        # output = self.relu1(input)
        output = self.sigmoid1(input)
        return output


linyu = Linyu()
writer = SummaryWriter("../logs/P16_logs")
step = 0
for data in dataloader:
    imgs, targets = data
    writer.add_images("input",imgs,step)
    # output = linyu(imgs)
    # writer.add_images("output",output,step)
    sigmoid1 = linyu(imgs)
    writer.add_images("sigmoid",sigmoid1,step)
    step += 1
writer.close()

输出:


激活函数.png

第一张是原始数据,第二张是经过ReLU激活函数的结果,第三张是经过sigmoid激活函数的结果。

参考资料:
1.https://blog.csdn.net/NIGHT_SILENT/article/details/80806644

你可能感兴趣的:(非线性激活函数)