神经网络系列---激活函数



激活函数

在这里插入图片描述

一般来说,在神经元中,激活函数是很重要的一部分,为了增强网络的表示能力和学习能力,神经网络的激活函数都是非线性的,通常具有以下几点性质:

  • 连续并可导(允许少数点上不可导),可导的激活函数可以直接利用数值优化的方法来学习网络参数;
  • 激活函数及其导数要尽可能简单一些,太复杂不利于提高网络计算率;
  • 激活函数的导函数值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性。

Sigmoid 激活函数

Sigmoid函数,也称为Logistic函数,是一种常用的激活函数之一。它将输入值映射到一个介于0和1之间的连续输出值。

Sigmoid函数的数学表达式为:

数学推导:
对Sigmoid函数f(x) = 1 / (1 + exp(-x)),我们可以通过链式法则对其求导。

首先,我们计算Sigmoid函数的导数f'(x):
f'(x) = d/dx(1 / (1 + exp(-x)))

接下来,我们将求导式进行变形,以便更方便地计算:
f'(x) = 1 / (1 + exp(-x))^2 * exp(-x)

因此,Sigmoid函数的导数f'(x)的表达式为:
f'(x) = f(x) * (1 - f(x))

这个表达式可以用于计算任意输入x处的Sigmoid函数的导数。

在这里插入图片描述

C++实现Sigmoid函数的示例代码:

#include <cmath>

// Sigmoid函数的实现
double sigmoid(double x) {
   
    return 1.0 / (1.0 + exp(-x));
}

// Sigmoid函数的导数实现
double sigmoidDerivative(double x) {
   
    double fx = sigmoid(x);
    return fx * (1.0 - fx);
}

int main() {
   
    double x = 2.0; // 示例输入值

    // 调用Sigmoid函数计算输出
    double result = sigmoid(x);

    // 调用Sigmoid函数的导数计算输出
    double derivative = sigmoidDerivative(x);

    // 输出结果
    printf("Sigmoid(%f) = %f\n", x, result);
    printf("Sigmoid的导数(%f) = %f\n", x, derivative);

    return 0;
}

其中,exp表示自然常数e(约等于2.71828)的指数函数。

Sigmoid函数的特点是在输入值较大或较小时,输出接近于1或0,而在输入值接近0时,输出接近于0.5。这种S型曲线形状使得Sigmoid函数在二分类问题中常被用作输出层的激活函数,将输出解释为概率值,表示正类的概率。

Sigmoid函数具有以下优点和缺点:

优点:

  1. 可以将输入映射到介于0和1之间的概率值,适用于二分类问题中将输出解释为概率的情况。
  2. Sigmoid函数在输入接近0时,输出接近于0.5,具有平滑的、连续的特性。
  3. Sigmoid函数具有可导性,这对于使用梯度下降等基于梯度的优化算法进行模型训练是重要的。

缺点:

  1. Sigmoid函数在输入较大或较小的情况下,输出接近于0或1,导致梯度饱和,使得反向传播时梯度变得非常小,造成梯度消失的问题。这限制了Sigmoid函数在深度神经网络中的应用。
  2. Sigmoid函数的指数计算较为复杂,相比于其他激活函数,计算代价较高。
  3. Sigmoid函数输出的值不是以0为中心的,即其输出均值不为0,这可能导致网络在训练过程中的收敛速度变慢。

综上所述,尽管Sigmoid函数在过去被广泛应用于神经网络中,但随着深度学习的发展,人们更倾向于使用其他激活函数,如ReLU及其变种,因为它们能够缓解梯度消失问题并提供更好的性能。然而,在某些特定情况下,Sigmoid函数仍然可以有所用处,例如需要将输出解释为概率的二分类问题。

Tanh激活函数

Tanh函数(双曲正切函数)是一种常用的激活函数,它将输入值映射到一个介于-1和1之间的连续输出值。

Tanh函数的数学表达式为:

数学推导:
Tanh函数的数学表达式为:

f(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x))

我们可以通过对Tanh函数进行求导,得到其导数的数学表达式。

首先,我们令y = Tanh(x),则Tanh函数可以表示为:

y = (exp(x) - exp(-x)) / (exp(x) + exp(-x))

对y求导,即计算dy/dx。

使用除法的求导法则和指数函数的求导法则,我们可以得到:

dy/dx = [(exp(x) + exp(-x))(exp(x) + exp(-x)) - (exp(x) - exp(-x))(exp(x) - exp(-x))] / (exp(x) + exp(-x))^2

化简上述表达式,我们可以得到Tanh函数的导数的数学表达式:

dy/dx = 1 - (Tanh(x))^2

因此,Tanh函数的导数为 1 减去其本身的平方。

在这里插入图片描述
Tanh函数可以看作是Sigmoid函数的变种,它具有Sigmoid函数的S型曲线形状,但输出范围更广,从-1到1。

Tanh函数的特点包括:

  1. 在输入接近0时,输出接近于0,具有零中心化的特性。
  2. Tanh函数的输出在输入为负时接近于-1,在输入为正时接近于1。
  3. Tanh函数是可导的,对于使用梯度下降等基于梯度的优化算法进行模型训练是可行的。

以下是使用C++实现Tanh函数的示例代码:

#include <cmath>

// Tanh函数的实现
double tanh(double x) {
   
    return (exp(x) - exp(-x)) / (exp(x) + exp(-x));
}

// Tanh函数的导数实现
double tanh_derivative(double x) {
   
    double tanh_x = tanh(x);
    return 1 - tanh_x * tanh_x;
}

int main() {
   
    double x = 2.0; // 示例输入值

    // 调用Tanh函数计算输出
    double result = tanh(x);

    // 调用Tanh函数的导数计算输出
    double derivative = tanh_derivative(x);

    // 输出结果
    printf("Tanh(%f) = %f\n", x, result);
    printf("Tanh Derivative(%f) = %f\n", x, derivative);

    return 0;
}

Tanh函数(双曲正切函数)具有以下优点和缺点:

优点:

  1. Tanh函数的输出范围是介于-1和1之间,相比于Sigmoid函数,Tanh函数的输出具有零中心化的特性,使得数据在处理时更接近原点。
  2. Tanh函数在输入接近0时,输出接近于0,可以将数据映射到更接近原点的区域,有助于模型的收敛。
  3. Tanh函数是可导的,对于使用梯度下降等基于梯度的优化算法进行模型训练是可行的。

缺点:

  1. Tanh函数在输入较大或较小的情况下,输出接近于1或-1,导致梯度饱和,使得反向传播时梯度变得非常小,造成梯度消失的问题,尤其在深度神经网络中。
  2. Tanh函数的指数计算较为复杂,相比于其他激活函数,计算代价较高。
  3. Tanh函数的输出值域为[-1, 1],这使得它对于某些任务而言,可能不是最优的激活函数选择。

综上所述,尽管Tanh函数具有一些优点,但在深度神经网络中,它容易出现梯度消失的问题,因此在实践中,ReLU及其变种等激活函数更常用。然而,Tanh函数仍然可以在特定情况下使用,例如需要将输出值范围控制在[-1, 1]之间的任务,或者在某些循环神经网络(RNN)的隐藏层中使用。

ReLU激活函数

ReLU(Rectified Linear Unit)函数是一种常用的激活函数,它在深度学习中广泛使用,特别是在卷积神经网络(CNN)中。

ReLU函数的定义很简单:

ReLU函数的数学表达式为:

f(x) = max(0, x)

ReLU函数在输入大于0时的导数为1,在输入小于等于0时的导数为0。

数学推导如下:

当 x > 0 时,ReLU函数为 f(x) = x。其导数为:

f'(x) = 1

当 x <= 0 时,ReLU函数为 f(x) = 0。其导数为:

f'(x) = 0

在这里插入图片描述

以下是使用C++实现ReLU激活函数的示例代码:

// ReLU函数的实现
double relu(double x) {
   
    return (x > 0) ? x : 0;
}

// ReLU函数的导数实现
double relu_derivative(double x) {
   
    return (x > 0) ? 1 : 0;
}

在这个示例代码中,relu()函数接受一个double类型的输入值x,并返回计算得到的ReLU函数的输出值。在函数内部,使用条件运算符(三元运算符)判断x是否大于0,如果是,则返回x,否则返回0。

main()函数中,我们提供一个示例输入值x,然后调用relu()函数计算输出结果,并使用printf()函数打印结果。

这只是一个简单的示例代码,您可以根据自己的需求进行扩展和修改。

即,对于输入x,如果x大于0,则输出x,否则输出0。

ReLU函数优缺点:
优点:

  1. 简单有效:ReLU函数的计算非常简单,只需比较输入和0的大小并取最大值。这使得ReLU函数的计算速度非常快,尤其对于大规模深度神经网络而言,具有重要意义。
  2. 解决梯度消失问题:相对于Sigmoid和Tanh等传统激活函数,ReLU函数在正区间(输入大于0)上具有线性特性,梯度恒定为1,不会出现梯度消失问题。这有助于有效传播梯度,促进模型的收敛。
  3. 增强稀疏性:ReLU函数在负区间(输入小于0)上的输出值为0,这种“激活稀疏性”可以使得神经网络中的许多神经元处于非激活状态,从而减少参数冗余和计算负载,提高模型的效率和泛化能力。

缺点:

  1. Dead ReLU问题:当输入小于等于0时,ReLU函数的输出恒为0,这时该神经元对应的权重无法更新,可能会导致神经元“死亡”,不再对任何输入产生响应。这一问题在训练过程中需要特别注意,可以通过使用Leaky ReLU或其他变种来缓解。
  2. 不是零中心化:ReLU函数的输出范围是从0开始的正半轴,不是以0为中心的。这可能导致某些优化算法对权重的更新过程受到一定影响。

综上所述,ReLU函数由于其简单性、解决梯度消失问题的能力和稀疏性增强的特性,成为了深度学习中最常用的激活函数之一。然而,在使用ReLU函数时,需要注意Dead ReLU问题和零中心化的影响,可能需要采取一些技巧和变种来处理这些问题。

Leaky ReLU激活函数

Leaky ReLU (Rectified Linear Unit) 是一种常用的激活函数,它是对传统的 ReLU 函数的改进。ReLU 函数在输入为负数时输出为零,这可能导致神经元的死亡,即该神经元在训练过程中不再激活。

为了解决这个问题,Leaky ReLU 引入了一个小的斜率(slope)参数,当输入为负数时,它会乘以这个斜率而不是输出零。这样,Leaky ReLU 允许一小部分负值通过,并给神经元提供了一个非零的输出。

Leaky ReLU 函数的数学定义如下:

f(x) = max(ax, x)

其中,a是一个小于1的正数,用来控制负值区域的斜率。

为了计算Leaky ReLU函数的导数,需要分别考虑两个区域:x > 0 和 x <= 0。

当 x > 0 时,函数的导数为1,即 f'(x) = 1。

当 x <= 0 时,函数的导数为 a,即 f'(x) = a。

因此,Leaky ReLU函数的导数可以表示为:

f'(x) = 1, if x > 0
        a, if x <= 0

在这里插入图片描述

其中,a 是一个小的正数,通常取较小的值,如0.01。当 a = 0 时,Leaky ReLU 退化为传统的 ReLU 函数。

Leaky ReLU 的主要优点是它在解决梯度消失问题方面比传统的 ReLU 函数更有效。由于在负值区域存在斜率,Leaky ReLU 可以传播梯度,使得神经网络的训练更稳定。此外,Leaky ReLU 保留了大部分的稀疏激活性质,即只有少数神经元被激活,这有助于减少模型的复杂性。

然而,Leaky ReLU 也存在一些缺点。由于负值区域的斜率是一个固定的超参数,它需要手动选择,并且可能会对模型的性能产生一定的影响。此外,Leaky ReLU 在解决梯度死亡问题方面仍然不如一些其他激活函数,如 ELU (Exponential Linear Unit) 或 SELU (Scaled Exponential Linear Unit)。

在实践中,Leaky ReLU 经常被用作默认的激活函数之一,并在很多深度学习模型中取得了良好的效果。

Leaky ReLU 的优缺点:
优点包括:

  1. 解决了梯度消失问题:相对于传统的 ReLU 函数,Leaky ReLU 具有非零的负斜率,这使得它能够传播梯度,有效地缓解了梯度消失的问题。

  2. 保留稀疏激活性质:Leaky ReLU 仍然保留了传统 ReLU 的稀疏激活性质,即只有少数神经元被激活,从而减少了模型的复杂性。

  3. 实现简单:Leaky ReLU 的计算简单,只需在传统的 ReLU 函数中引入一个小的斜率参数即可。

缺点:

  1. 需要手动选择超参数:Leaky ReLU 的性能依赖于负斜率参数的选择,这需要手动调整。选择不当的斜率参数可能会导致模型的性能下降。

  2. 可能存在神经元死亡问题:尽管 Leaky ReLU 解决了传统 ReLU 中负输入值输出为零的问题,但仍然可能存在神经元死亡问题。当斜率参数选择得过小时,仍有一部分神经元在训练过程中不会被激活。

  3. 不是最优选择:虽然 Leaky ReLU 在某些情况下表现良好,但并不是最优的激活函数选择。一些其他激活函数,如 ELU 或 SELU,可以在解决梯度消失问题方面更有效,并提供更平滑的激活函数曲线。

以下是使用C++实现Leaky ReLU激活函数的示例代码:

// Leaky ReLU函数的实现
double leakyRelu(double x, double alpha) {
   
    return (x > 0) ? x : alpha * x;
}

// Leaky ReLU函数的导数实现
double leakyReluDerivative(double x, double alpha) {
   
    return (x > 0) ? 1 : alpha;
}

int main() {
   
    double x = -2.0; // 示例输入值
    double alpha = 0.1; // Leaky ReLU的负斜率

    // 调用Leaky ReLU函数计算输出
    double result = leakyRelu(x, alpha);

    // 输出结果
    printf("Leaky ReLU(%f) = %f\n", x, result);

    return 0;
}

Parametric ReLU激活函数 (自适应Leaky ReLU激活函数)

Parametric ReLU (PReLU) 是一种激活函数,它是对传统的 ReLU 函数的改进。与 Leaky ReLU 不同,PReLU 的负斜率不是固定的超参数,而是可以通过学习得到的可训练参数。

PReLU 的数学定义如下:
f(x) = max(ax, x)

其中a是一个可学习的参数,可以根据数据进行训练得到。当a为0时,PReLU函数退化为普通的ReLU函数。

PReLU函数的导数在正区间(x>0)上为1,而在负区间(x<0)上为a。下面我们来推导PReLU函数的导数。

对于x>0,PReLU函数的导数为1:
f'(x) = 1,(x > 0)

对于x<0,PReLU函数的导数为a:
f'(x) = a,(x < 0)

在x=0的位置,PReLU函数的导数存在争议,通常会将其定义为左导数和右导数的平均值,即:
f'(x) = (1 + a) / 2,(x = 0)

这样,PReLU函数的导数就可以在所有实数范围内连续定义。

其中,a 是一个可训练的参数,它可以根据数据进行学习和调整。当 a = 0 时,PReLU 退化为传统的 ReLU 函数。

Parametric ReLU优缺点:
优点:

  1. 自适应负斜率:PReLU 允许负斜率参数根据数据进行学习,这使得激活函数能够自适应地调整负值区域的斜率。这提供了更大的灵活性和表达能力,适应不同类型的数据分布和模型复杂度。

  2. 解决了梯度消失问题:与传统的 ReLU 类似,PReLU 仍然能够缓解梯度消失问题,通过保留正值输入的激活性。

缺点:

  1. 参数学习的复杂性:与固定斜率的 Leaky ReLU 不同,PReLU 需要学习一个额外的参数,这增加了模型的复杂性。参数的学习过程需要更多的计算和存储资源,并且可能需要更多的数据和训练时间。

  2. 过拟合的风险:PReLU 具有更高的模型灵活性,这也可能导致过拟合的风险增加。过度学习负斜率参数可能使模型对训练数据过度拟合,从而影响泛化能力。

在实践中,PReLU 可以作为一种有效的激活函数,特别适用于深层神经网络和大规模数据集。然而,由于其参数学习的复杂性,需要在具体问题和实验中进行评估和比较,以确定是否使用 PReLU 和如何设置参数。

以下是使用C++实现PReLU激活函数及其导数的示例代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值