多角度理解sigmoid,relu和tanh激活函数

  • sigmoid激活函数

函数表达式:

函数图像:

 通过该图,可以看出对于sigmoid激活函数的一下几个特点:

1.当输入大于零时,输出一定大于0.5

2.当输入超过6时,输出将会无限接近于1,该函数的辨别功能将会失效

3.函数的阈值在0,1之间

4.具有良好的对称性

根据上述几个特点,我们可以初步总结sigmoid函数的应用场景:sigmoid的输出在0和1之间,我们在二分类任务中,采用sigmoid的输出的事件概率,也就是当输出满足满足某一概率条件我们将其划分分类,所以sigmoid函数适合作为二分类问题的输出层。

下面通过几个角度更加深入的理解sigmoid函数:

一.通过生活中的实例解释sigmoid激活函数:

题目:已知某样本实验者的身高,体重,每日锻炼时长,猜测实验者的性别。

首先根据sigmoid的特性,即当输入大于6时函数将会基本失效,则我们设定评判身高体重和每日锻炼时长的最高分为6,这里先估计某人的身高为5,体重为4.4,腰围为4,锻炼时长为4.3

然后设置身高的权重为0.3,体重的权重为4.4,腰围的权重为0.2,锻炼时长的权重为0.2

对于阈值,设置0到0.6为女生,0.6到1为男生

通过如上计算,可以得到结果为0.96,则判断该人很有可能是男生。

但是经过笔者在多次计算后发现之前说的通过分值代替身高体重的方法输入的数值还是太大了,如果输入的数大部分在3左右,则最后得出基本都是在0.9以上,很难有较好的判别效果分类,所以下述中,使用厘米计算身高,吨计算体重,小时计算锻炼时长,先简化放弃腰围,再次计算。

在上述计算中,通过第一次计算将阈值分界点从0.5改到了0.62,于是判断出了性别。

通过上述实验可以看出,sigmoid激活函数适合于在输出层作为二分类实验,以输出结果。

二.通过bp神经网络代码实验sigmoid激活函数

sigmoid函数代码定义:

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

使用sigmoid激活函数实现bp神经网络的完整代码,该神经网络是通过前面的跳高成绩去预测下一次跳高是成功还是失败,其中1代表成功0代表失败:

import numpy as np


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def main():
    # 14条数据
    data = np.array([
        [1, 0, 0, 1, 0, 1, 1, 1],
        [0, 0, 1, 1, 0, 0, 1, 0],
        [1, 1, 0, 1, 1, 1, 0, 1],
        [0, 1, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 1, 0, 1, 1, 1],
        [1, 1, 0, 0, 1, 1, 1, 0],
        [0, 0, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 1, 0, 1, 1, 1],
        [1, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 1, 0, 1, 1],
        [1, 0, 0, 1, 0, 1, 1, 0],
        [0, 0, 1, 1, 0, 1, 0, 1],
        [1, 0, 0, 1, 0, 0, 1, 1],
        [0, 1, 0, 1, 0, 1, 1, 1]])
    print("原始数据:\n", data)
    # 十四条数据的跳高成绩
    highJump = np.array(
        [0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0])
    print("十四条数据的跳高成绩:\n", highJump)
    # 第十五条数据的输入
    data15 = np.array([0, 1, 0, 1, 1, 0, 1, 0])
    print("第十五条数据的输入:\n", data15)
    # 设置输入层与隐藏层之间的权值和阈值
    wInput = np.random.random(size=(6, 8)) / 10
    print("输入层与隐藏层之间的六组权值:\n", wInput)
    bInput = np.random.random(size=(6, 8)) / 10
    print("输入层与隐藏层之间的六组阈值:\n", bInput)
    # 设置隐藏层与输出层之间的权值和阈值
    wOutput = np.random.random(size=6) / 10
    print("隐藏层与输出层之间的一组权值", wOutput)
    bOutput = np.random.random(size=6) / 10
    print("隐藏层与输出层之间的一组阈值", bOutput)
    loss = 2
    count = 0
    while loss > 1.7489:
        count = count + 1
        loss = 0
        outputNode = []
        for i in range(0, 14):

            # 正向传播
            # 计算隐藏层节点输入
            hide = []
            for j in range(0, 6):
                hideNode = 0
                for k in range(0, 8):
                    hideNode = data[i, k] * wInput[j, k] + \
                               bInput[j, k] + hideNode
                #print(hideNode)
                hideNode = sigmoid(hideNode)  # 激活函数
                hide.append(hideNode)
            hide = np.array(hide)
            # print("隐藏层结点", hide)
            output = 0
            for j in range(0, 6):
                output = hide[j] * wOutput[j] + bOutput[j] + output
            output = sigmoid(output)
            outputNode.append(output)
            # print("输出层结点", output)
            loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
        outputNode = np.array(outputNode)
        # 反向传播
        # print("隐藏层结点", hide)
        for i in range(0, 14):
            # 隐藏层与输出层之间权值阈值更新
            wOutputLoss = []
            for j in range(0, 6):
                wOutputLoss.append((outputNode[i] - highJump[i]) *
                                   outputNode[i] * (1 - outputNode[i])
                                   * hide[j])
            wOutputLoss = np.array(wOutputLoss)
            # print("wOutputLoss", wOutputLoss)
            bOutputLoss = []
            for j in range(0, 6):
                bOutputLoss.append((outputNode[i] - highJump[i]) *
                                   outputNode[i] * (1 - outputNode[i]))
            bOutputLoss = np.array(bOutputLoss)
            # print("bOutputLoss", bOutputLoss)
            for j in range(0, 6):
                wOutput[j] = wOutput[j] - 0.1 * wOutputLoss[j]
                bOutput[j] = bOutput[j] - 0.1 * bOutputLoss[j]
            # print("隐藏层与输出层更新后权值和阈值", wOutput, bOutput)
            # 输入层与隐藏层之间权值更新
            wInputLoss = np.ones((6, 8)) * 0
            for j in range(0, 6):
                for k in range(0, 8):
                    wInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
                                        outputNode[i] *
                                        (1 - outputNode[i]) * wOutput[j]
                                        * hide[j] * (1 - hide[j]) * data[i][k])
            wInputLoss = np.array(wInputLoss)
            # print("wIutputLoss", wInputLoss)
            bInputLoss = np.ones((6, 8)) * 0
            for j in range(0, 6):
                for k in range(0, 8):
                    bInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
                                        outputNode[i] * (1 - outputNode[i]) *
                                        wOutput[j] * hide[j] * (1 - hide[j]))
            bInputLoss = np.array(bInputLoss)
            #print("bIutputLoss", bInputLoss)
            for j in range(0, 6):
                for k in range(0, 8):
                    wInput[j][k] = wInput[j][k] - 0.1 * wInputLoss[j][k]
                    bInput[j][k] = bInput[j][k] - 0.1 * bInputLoss[j][k]
            #print("输入层与隐藏层之间更新后的权值和阈值", wInput, bInput)
        print("输出", output)
        print("学习前的loss", loss)
        loss = 0
        for i in range(0, 14):
            # 正向传播
            # 计算隐藏层节点输入
            hide = []
            for j in range(0, 6):
                hideNode = 0
                for k in range(0, 8):
                    hideNode = data[i, k] * wInput[j, k] + \
                               bInput[j, k] + hideNode
                hideNode = sigmoid(hideNode)  # 激活函数
                hide.append(hideNode)
            hide = np.array(hide)
            output = 0
            for j in range(0, 6):
                output = hide[j] * wOutput[j] + bOutput[j] + output
            output = sigmoid(output)
            loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
        print("输出", output)
        print("学习后的loss", loss)

    # 预测
    hide = []
    for j in range(0, 6):
        hideNode = 0
        for k in range(0, 8):
            hideNode = data15[k] * wInput[j, k] + \
                       bInput[j, k] + hideNode
            hideNode = sigmoid(hideNode)  # 激活函数
        hide.append(hideNode)
    hide = np.array(hide)
    output = 0
    for j in range(0, 6):
        output = hide[j] * wOutput[j] + bOutput[j] + output
    output = sigmoid(output)
    print(output)
    print(loss)
    print(count)


if __name__ == '__main__':
    main()

该bp神经网络包括了带有八个神经元(也就是原始数据中的八个01成绩)的输入层,带有六个神经元的隐藏层和一个神经元的输出层。

运行代码后得到结果:

上图是该网络首次完成学习前后的输出以及损失

上图是在第297次学习前后的输出以及损失并输出了最终的输出和损失,可以看到最后输出小于0.5,预测在下一次测试中较为有可能失败的。

  • tanh激活函数

函数表达式:
 

函数图像:

 

Tanh函数也称为双曲正切函数,取值范围为[-1,1]。Tanh函数是 sigmoid 的变形,Tanh函数是 0 均值的,因此实际应用中 Tanh 会比 sigmoid 更好。

通过上图,结合sigmoid函数可以得到tanh函数的几个特点:

1.双曲正切函数,tanh函数输出以0为中心,区间为[−1,1],tanh可以想象成两个sigmoid函数放在一起,性能要高于sigmoid函数

2.收敛速度相对于Sigmoid更快

3.但是仍然存在梯度饱和与exp计算的问题。

依据上述特点,可以初步总结tanh函数的应用场景:由于tanh是sigmoid的改进版,所以sigmoid函数的应用场景tanh也都可以用,而且tanh性能更加优异,所以在不确定激活函数的时候使用tanh进行尝试的优先级仅仅在relu之后,不同于sigmoid函数适用于输出层的特性,tanh函数还可以应用在隐层和更多的情况。

和之前一样下面通过两个角度更加输入的理解tanh函数

一.通过生活中的实例解释tanh激活函数:

使用之前的题目:已知某样本实验者的身高,体重,每日锻炼时长,猜测实验者的性别。

在上述阈值中可以判断出性别,tanh函数可以使用在sigmoid适用的场景。

二.通过bp神经网络代码实验tanh激活函数

tanh函数定义代码:

def tanh(x):
    return np.sinh(x)/np.cosh(x)

使用上文中的跳高预测代码,稍作修改得到如下代码:

import numpy as np


def tanh(x):
    return np.sinh(x)/np.cosh(x)


def main():
    # 14条数据
    data = np.array([
        [1, 0, 0, 1, 0, 1, 1, 1],
        [0, 0, 1, 1, 0, 0, 1, 0],
        [1, 1, 0, 1, 1, 1, 0, 1],
        [0, 1, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 1, 0, 1, 1, 1],
        [1, 1, 0, 0, 1, 1, 1, 0],
        [0, 0, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 1, 0, 1, 1, 1],
        [1, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 1, 0, 1, 1],
        [1, 0, 0, 1, 0, 1, 1, 0],
        [0, 0, 1, 1, 0, 1, 0, 1],
        [1, 0, 0, 1, 0, 0, 1, 1],
        [0, 1, 0, 1, 0, 1, 1, 1]])
    print("原始数据:\n", data)
    # 十四条数据的跳高成绩
    highJump = np.array(
        [0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0])
    print("十四条数据的跳高成绩:\n", highJump)
    # 第十五条数据的输入
    data15 = np.array([0, 1, 0, 1, 1, 0, 1, 0])
    print("第十五条数据的输入:\n", data15)
    # 设置输入层与隐藏层之间的权值和阈值
    wInput = np.random.random(size=(6, 8)) / 10
    print("输入层与隐藏层之间的六组权值:\n", wInput)
    bInput = np.random.random(size=(6, 8)) / 10
    print("输入层与隐藏层之间的六组阈值:\n", bInput)
    # 设置隐藏层与输出层之间的权值和阈值
    wOutput = np.random.random(size=6) / 10
    print("隐藏层与输出层之间的一组权值", wOutput)
    bOutput = np.random.random(size=6) / 10
    print("隐藏层与输出层之间的一组阈值", bOutput)
    loss = 2
    count = 0
    while loss < 2.1:
        count = count + 1
        loss = 0
        outputNode = []
        for i in range(0, 14):

            # 正向传播
            # 计算隐藏层节点输入
            hide = []
            for j in range(0, 6):
                hideNode = 0
                for k in range(0, 8):
                    hideNode = data[i, k] * wInput[j, k] + \
                               bInput[j, k] + hideNode
                #print(hideNode)
                hideNode = tanh(hideNode)  # 激活函数
                hide.append(hideNode)
            hide = np.array(hide)
            # print("隐藏层结点", hide)
            output = 0
            for j in range(0, 6):
                output = hide[j] * wOutput[j] + bOutput[j] + output
            output = tanh(output)
            outputNode.append(output)
            # print("输出层结点", output)
            loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
        outputNode = np.array(outputNode)
        # 反向传播
        # print("隐藏层结点", hide)
        for i in range(0, 14):
            # 隐藏层与输出层之间权值阈值更新
            wOutputLoss = []
            for j in range(0, 6):
                wOutputLoss.append((outputNode[i] - highJump[i]) *
                                   outputNode[i] * (1 - outputNode[i])
                                   * hide[j])
            wOutputLoss = np.array(wOutputLoss)
            # print("wOutputLoss", wOutputLoss)
            bOutputLoss = []
            for j in range(0, 6):
                bOutputLoss.append((outputNode[i] - highJump[i]) *
                                   outputNode[i] * (1 - outputNode[i]))
            bOutputLoss = np.array(bOutputLoss)
            # print("bOutputLoss", bOutputLoss)
            for j in range(0, 6):
                wOutput[j] = wOutput[j] - 0.1 * wOutputLoss[j]
                bOutput[j] = bOutput[j] - 0.1 * bOutputLoss[j]
            # print("隐藏层与输出层更新后权值和阈值", wOutput, bOutput)
            # 输入层与隐藏层之间权值更新
            wInputLoss = np.ones((6, 8)) * 0
            for j in range(0, 6):
                for k in range(0, 8):
                    wInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
                                        outputNode[i] *
                                        (1 - outputNode[i]) * wOutput[j]
                                        * hide[j] * (1 - hide[j]) * data[i][k])
            wInputLoss = np.array(wInputLoss)
            # print("wIutputLoss", wInputLoss)
            bInputLoss = np.ones((6, 8)) * 0
            for j in range(0, 6):
                for k in range(0, 8):
                    bInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
                                        outputNode[i] * (1 - outputNode[i]) *
                                        wOutput[j] * hide[j] * (1 - hide[j]))
            bInputLoss = np.array(bInputLoss)
            #print("bIutputLoss", bInputLoss)
            for j in range(0, 6):
                for k in range(0, 8):
                    wInput[j][k] = wInput[j][k] - 0.1 * wInputLoss[j][k]
                    bInput[j][k] = bInput[j][k] - 0.1 * bInputLoss[j][k]
            #print("输入层与隐藏层之间更新后的权值和阈值", wInput, bInput)
        print("输出", output)
        print("学习前的loss", loss)
        loss = 0
        for i in range(0, 14):
            # 正向传播
            # 计算隐藏层节点输入
            hide = []
            for j in range(0, 6):
                hideNode = 0
                for k in range(0, 8):
                    hideNode = data[i, k] * wInput[j, k] + \
                               bInput[j, k] + hideNode
                hideNode = tanh(hideNode)  # 激活函数
                hide.append(hideNode)
            hide = np.array(hide)
            output = 0
            for j in range(0, 6):
                output = hide[j] * wOutput[j] + bOutput[j] + output
            output = tanh(output)
            loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
        print("输出", output)
        print("学习后的loss", loss)

    # 预测
    hide = []
    for j in range(0, 6):
        hideNode = 0
        for k in range(0, 8):
            hideNode = data15[k] * wInput[j, k] + \
                       bInput[j, k] + hideNode
            hideNode = tanh(hideNode)  # 激活函数
        hide.append(hideNode)
    hide = np.array(hide)
    output = 0
    for j in range(0, 6):
        output = hide[j] * wOutput[j] + bOutput[j] + output
    output = tanh(output)
    print(output)
    print(loss)
    print(count)


if __name__ == '__main__':
    main()

上述代码和之前的bp神经网络有着相同的层,每层有相同的神经元,仅对loss结果和激活函数进行了修改。

上述代码在一开始的运行结果如图:
 

结果多次学习后的代码如图:

可见学习了506次之后,学习后的损失值反而更大了,这里可能是由于过拟合之类的问题造成的,

不过可以初步判定该段代码相对于sigmoid函数不是很适合tanh激活函数,也可以看到在该段代码中将tanh用于输出层的效果不如sigmoid。

  • relu激活函数

relu函数:

relu函数图像:

relu函数的使用情况:

1.二分类问题中除了输出层外全部用relu

2.在不确定的情况下一般优先考虑relu

3.relu中负数会直接变为0

4.遇到死的神经元也会使用relu

relu函数的特点:

整流线性单元(Rectified linear unit,ReLU)是现代神经网络中最常用的激活函数,大多数前馈神经网络默认使用的激活函数。

优点:

1.使用ReLU的SGD算法的收敛速度比 sigmoid 和 tanh 快。

2. 在x>0区域上,不会出现梯度饱和、梯度消失的问题。

3. 计算复杂度低,不需要进行指数运算,只要一个阈值就可以得到激活值。

缺点:

ReLU的输出不是0均值(将每个像素的值减去训练集上所有像素值的平均值,比如已计算得所有像素点的平均值为128,所以减去128后,现在的像素值域即为[-128,127],即满足均值为零。的。

relu函数的例子笔者就不再算一次了,基本算法思路和代码和上文一样。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Sigmoid 函数是一种非线性函数,它的输出值介于 0 和 1 之间,可以用来激活神经元。Tanh 函数也是一种非线性函数,其输出值也介于 -1 和 1 之间,可以用来激活神经元。ReLU 函数是一种常用的非线性函数,它的输出值是输入值的绝对值,可以用来激活神经元。 ### 回答2: Sigmoid函数Tanh函数和ReLU函数都是常用的激活函数,常用于神经网络模型中。 1. Sigmoid函数是一个非线性的函数,其数学定义为:f(z) = 1 / (1 + exp(-z))。它有一个S形的曲线,取值范围在0到1之间。Sigmoid函数的主要特点是将输入的连续实数转化为概率形式的输出,常用于二分类问题中,可以将输出映射到0和1之间,表示了某个事件发生的概率。然而,由于其容易出现梯度消失和梯度爆炸的问题,当网络层数较多时,Sigmoid函数在反向传播中可能导致梯度无法有效地传播。 2. Tanh函数是双曲线正切函数,其数学定义为:f(z) = (exp(z) - exp(-z)) / (exp(z) + exp(-z))。类似于Sigmoid函数Tanh函数也是非线性函数,但其输出范围在-1到1之间。相比于Sigmoid函数Tanh函数在原点附近有一个均值为0的对称点,具有更好的中心化特性,可以减小梯度爆炸的问题。然而,Tanh函数仍然存在梯度消失的问题。 3. ReLU函数是修正线性单元函数,其数学定义为:f(z) = max(0, z)。ReLU函数在输入大于零时输出等于输入,小于零时输出为零。ReLU函数具有简单的计算形式,并且在训练过程中具有更快的收敛速度。由于ReLU函数的输出非负,不存在梯度消失的问题。然而,ReLU函数在输入为负时会失活,导致相应神经元的权重和梯度无法进行更新。为解决这个问题,出现了ReLU的变种,如Leaky ReLU、PReLU等。 总结来说,Sigmoid函数Tanh函数在某些场景下仍然有一定的应用,但在深度神经网络中,ReLU函数更受欢迎,因为它可以在一定程度上减轻梯度消失和梯度爆炸问题,并提供更快的训练速度。 ### 回答3: Sigmoid函数是一种常用的激活函数,它将输入的实数映射到一个介于0和1之间的概率值。其公式为: \[f(x) = \frac{1}{1 + e^{-x}}\] 该函数的特点是输出在区间(0,1)之间,对于大部分实数输入都能产生有效的梯度,但在输入接近两端的时候,梯度会变得很小。因此,Sigmoid函数在深度神经网络的训练过程中可能会出现梯度消失的问题。 Tanh函数是双曲正切函数,它将输入的实数映射到一个介于-1和1之间的值。其公式为: \[f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}\] 与Sigmoid函数相比,Tanh函数的输出区间更大,梯度也更大。它相对于原点对称,并且输入为负数时,输出接近-1,输入为正数时,输出接近1。因此,Tanh函数适用于输出介于-1和1之间的情况,但仍然存在梯度消失的问题。 ReLU函数是修正线性单元函数,它将输入的实数映射为输入本身或者0。其公式为: \[f(x) = max(0, x)\] ReLU函数当输入为正数时,输出等于输入;当输入为负数时,输出为0。相较于Sigmoid函数Tanh函数,ReLU函数计算速度更快,并且不存在梯度消失的问题。然而,ReLU函数也存在一个缺点,就是在输入为负数时,梯度为0,从而导致对应的权重无法更新。为了解决这个问题,一些改进的版本如Leaky ReLU和Parametric ReLU被提出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值