【pytorch(08)】激活函数:sigmoid,tanh,ReLU,LeakyReLU,softmax

【pytorch(01)】CUDA、cuDNN、pytorch安装,神奇魔法解决网络问题

【pytorch(02)】Tensor(张量)概述、如何创建、常见属性,切换设备

【pytorch(03)】Tensor(张量)数据转换、常见操作(元素值运算、阿达玛积、相乘、形状操作)

【pytorch(04)】自动微分:基础概念,梯度计算,梯度上下文控制

【pytorch(05)】感知神经网络,构造人工神经元,深入神经网络(了解结构,如何构建),全连接神经网络

【pytorch(06)】全连接神经网络:基本组件认知,线性层、激活函数、损失函数、优化器

【pytorch(07)】数据加载器,构建数据类Dataset类、TensorDataset类,数据集加载案例

基础概念

通过认识线性和非线性的基础概念,深刻理解激活函数存在的价值。
激活函数理解视频:[5分钟深度学习] #03 激活函数

1.线性理解

如果在隐藏层不使用激活函数,那么整个神经网络会表现为一个线性模型。我们可以通过数学推导来展示这一点。

假设:

  • 神经网络有 L L L 层,每层的输出为 a ( l ) \mathbf{a}^{(l)} a(l)
  • 每层的权重矩阵为 W ( l ) \mathbf{W}^{(l)} W(l),偏置向量为 b ( l ) \mathbf{b}^{(l)} b(l)
  • 输入数据为 x \mathbf{x} x,输出为 a ( L ) \mathbf{a}^{(L)} a(L)

一层网络的情况

对于单层网络(输入层到输出层),如果没有激活函数,输出 a ( 1 ) \mathbf{a}^{(1)} a(1) 可以表示为:
a ( 1 ) = W ( 1 ) x + b ( 1 ) \mathbf{a}^{(1)} = \mathbf{W}^{(1)} \mathbf{x} + \mathbf{b}^{(1)} a(1)=W(1)x+b(1)

两层网络的情况

假设我们有两层网络,且每层都没有激活函数,则:

  • 第一层的输出: a ( 1 ) = W ( 1 ) x + b ( 1 ) \mathbf{a}^{(1)} = \mathbf{W}^{(1)} \mathbf{x} + \mathbf{b}^{(1)} a(1)=W(1)x+b(1)
  • 第二层的输出: a ( 2 ) = W ( 2 ) a ( 1 ) + b ( 2 ) \mathbf{a}^{(2)} = \mathbf{W}^{(2)} \mathbf{a}^{(1)} + \mathbf{b}^{(2)} a(2)=W(2)a(1)+b(2)

a ( 1 ) \mathbf{a}^{(1)} a(1)代入到 a ( 2 ) \mathbf{a}^{(2)} a(2)中,可以得到:

a ( 2 ) = W ( 2 ) ( W ( 1 ) x + b ( 1 ) ) + b ( 2 ) \mathbf{a}^{(2)} = \mathbf{W}^{(2)} (\mathbf{W}^{(1)} \mathbf{x} + \mathbf{b}^{(1)}) + \mathbf{b}^{(2)} a(2)=W(2)(W(1)x+b(1))+b(2)

a ( 2 ) = W ( 2 ) W ( 1 ) x + W ( 2 ) b ( 1 ) + b ( 2 ) \mathbf{a}^{(2)} = \mathbf{W}^{(2)} \mathbf{W}^{(1)} \mathbf{x} + \mathbf{W}^{(2)} \mathbf{b}^{(1)} + \mathbf{b}^{(2)} a(2)=W(2)W(1)x+W(2)b(1)+b(2)

我们可以看到,输出 a ( 2 ) \mathbf{a}^{(2)} a(2)是输入 x \mathbf{x} x的线性变换,因为: a ( 2 ) = W ′ x + b ′ \mathbf{a}^{(2)} = \mathbf{W}' \mathbf{x} + \mathbf{b}' a(2)=Wx+b
其中 W ′ = W ( 2 ) W ( 1 ) \mathbf{W}' = \mathbf{W}^{(2)} \mathbf{W}^{(1)} W=W(2)W(1) b ′ = W ( 2 ) b ( 1 ) + b ( 2 ) \mathbf{b}' = \mathbf{W}^{(2)} \mathbf{b}^{(1)} + \mathbf{b}^{(2)} b=W(2)b(1)+b(2)

多层网络的情况

如果有 L L L层,每层都没有激活函数,则第 l l l层的输出为: a ( l ) = W ( l ) a ( l − 1 ) + b ( l ) \mathbf{a}^{(l)} = \mathbf{W}^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} a(l)=W(l)a(l1)+b(l)

通过递归代入,可以得到:
a ( L ) = W ( L ) W ( L − 1 ) ⋯ W ( 1 ) x + W ( L ) W ( L − 1 ) ⋯ W ( 2 ) b ( 1 ) + W ( L ) W ( L − 1 ) ⋯ W ( 3 ) b ( 2 ) + ⋯ + b ( L ) \mathbf{a}^{(L)} = \mathbf{W}^{(L)} \mathbf{W}^{(L-1)} \cdots \mathbf{W}^{(1)} \mathbf{x} + \mathbf{W}^{(L)} \mathbf{W}^{(L-1)} \cdots \mathbf{W}^{(2)} \mathbf{b}^{(1)} + \mathbf{W}^{(L)} \mathbf{W}^{(L-1)} \cdots \mathbf{W}^{(3)} \mathbf{b}^{(2)} + \cdots + \mathbf{b}^{(L)} a(L)=W(L)W(L1)W(1)x+W(L)W(L1)W(2)b(1)+W(L)W(L1)W(3)b(2)++b(L)
表达式可简化为:
a ( L ) = W ′ ′ x + b ′ ′ \mathbf{a}^{(L)} = \mathbf{W}'' \mathbf{x} + \mathbf{b}'' a(L)=W′′x+b′′
其中, W ′ ′ \mathbf{W}'' W′′ 是所有权重矩阵的乘积, b ′ ′ \mathbf{b}'' b′′是所有偏置项的线性组合。

如此可以看得出来,无论网络多少层,意味着:

整个网络就是线性模型,无法捕捉数据中的非线性关系。

激活函数是引入非线性特性、使神经网络能够处理复杂问题的关键。

1.非线性可视化

我们可以通过可视化的方式去理解非线性的拟合能力:https://playground.tensorflow.org/
在这里插入图片描述

常见激活函数

激活函数通过引入非线性来增强神经网络的表达能力,对于解决线性模型的局限性至关重要。由于反向传播算法(BP)用于更新网络参数,因此激活函数必须是可微的,也就是说能够求导的。

1.sigmoid

Sigmoid激活函数是一种常见的非线性激活函数,特别是在早期神经网络中应用广泛。它将输入映射到0到1之间的值,因此非常适合处理概率问题。

1.1 公式

Sigmoid函数的数学表达式为:
f ( x ) = σ ( x ) = 1 1 + e − x f(x) = \sigma(x) = \frac{1}{1 + e^{-x}} f(x)=σ(x)=1+ex1
其中, e e e 是自然常数(约等于2.718), x x x 是输入。

1.2 特征

  1. 将任意实数输入映射到 (0, 1)之间,因此非常适合处理概率场景。

  2. sigmoid函数一般只用于二分类的输出层。

  3. 微分性质: 导数计算比较方便,可以用自身表达式来表示:
    σ ′ ( x ) = σ ( x ) ⋅ ( 1 − σ ( x ) ) \sigma'(x)=\sigma(x)\cdot(1-\sigma(x)) σ(x)=σ(x)(1σ(x))

1.3 缺点

  • 梯度消失:
    • 在输入非常大或非常小时,Sigmoid函数的梯度会变得非常小,接近于0。这导致在反向传播过程中,梯度逐渐衰减。
    • 最终使得早期层的权重更新非常缓慢,进而导致训练速度变慢甚至停滞。
  • 信息丢失:输入100和输入10000经过sigmoid的激活值几乎都是等于 1 的,但是输入的数据却相差 100 倍。
  • 计算成本高: 由于涉及指数运算,Sigmoid的计算比ReLU等函数更复杂,尽管差异并不显著。

1.4 函数绘制

通过代码实现函数和导函数绘制:

import torch
import matplotlib.pyplot as plt

# plt支持中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test001():
    # 一行两列绘制图像
    _, ax = plt.subplots(1, 2)
    # 绘制函数图像
    x = torch.linspace(-10, 10, 100)
    y = torch.sigmoid(x)
    # 网格
    ax[0].grid(True)
    ax[0].set_title("sigmoid 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    # 在第一行第一列绘制sigmoid函数曲线图
    ax[0].plot(x, y)

    # 绘制sigmoid导数曲线图
    x = torch.linspace(-10, 10, 100, requires_grad=True)
    # y = torch.sigmoid(x) * (1 - torch.sigmoid(x))
    # 自动求导
    torch.sigmoid(x).sum().backward()
    ax[1].grid(True)
    ax[1].set_title("sigmoid 函数导数曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("y")
    # ax[1].plot(x.detach().numpy(), y.detach())
    # 用自动求导的结果绘制曲线图
    ax[1].plot(x.detach().numpy(), x.grad.detach().numpy())
    # 设置曲线颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test001()

绘制结果:

在这里插入图片描述

2.tanh

tanh(双曲正切)是一种常见的非线性激活函数,常用于神经网络的隐藏层。tanh 函数也是一种S形曲线,输出范围为 ( − 1 , 1 ) (−1,1) (1,1)

2.1 公式

tanh数学表达式为:
t a n h ( x ) = e x − e − x e x + e − x {tanh}(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+exexex

2.2 特征

  1. 输出范围: 将输入映射到 ( − 1 , 1 ) (-1, 1) (1,1)之间,因此输出是零中心的。相比于Sigmoid函数,这种零中心化的输出有助于加速收敛。

  2. 对称性: Tanh函数是关于原点对称的奇函数,因此在输入为0时,输出也为0。这种对称性有助于在训练神经网络时使数据更平衡。

  3. 平滑性: Tanh函数在整个输入范围内都是连续且可微的,这使其非常适合于使用梯度下降法进行优化。
    d d x tanh ( x ) = 1 − tanh 2 ( x ) \frac{d}{dx} \text{tanh}(x) = 1 - \text{tanh}^2(x) dxdtanh(x)=1tanh2(x)

2.3 缺点

  1. 梯度消失: 虽然一定程度上改善了梯度消失问题,但在输入值非常大或非常小时导数还是非常小,这在深层网络中仍然是个问题。这是因为每一层的梯度都会乘以一个小于1的值,经过多层乘积后,梯度会变得非常小,导致训练过程变得非常缓慢,甚至无法收敛。
  2. 计算成本: 由于涉及指数运算,Tanh的计算成本还是略高,尽管差异不大。

2.4 函数绘制

绘制代码:

import torch
import matplotlib.pyplot as plt

# plt支持中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test001():
    # 一行两列绘制图像
    _, ax = plt.subplots(1, 2)
    # 绘制函数图像
    x = torch.linspace(-10, 10, 100)
    y = torch.tanh(x)
    # 网格
    ax[0].grid(True)
    ax[0].set_title("tanh 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    # 在第一行第一列绘制tanh函数曲线图
    ax[0].plot(x, y)

    # 绘制tanh导数曲线图
    x = torch.linspace(-10, 10, 100, requires_grad=True)
    # y = torch.tanh(x) * (1 - torch.tanh(x))
    # 自动求导:需要标量才能反向传播
    torch.tanh(x).sum().backward()
    ax[1].grid(True)
    ax[1].set_title("tanh 函数导数曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")
    # ax[1].plot(x.detach().numpy(), y.detach())
    # 用自动求导的结果绘制曲线图
    ax[1].plot(x.detach().numpy(), x.grad.detach().numpy())
    # 设置曲线颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test001()

绘制结果:

在这里插入图片描述

3.ReLU

ReLU(Rectified Linear Unit)是深度学习中最常用的激活函数之一,它的全称是修正线性单元。ReLU 激活函数的定义非常简单,但在实践中效果非常好。

3.1 公式

ReLU 函数定义如下:
ReLU ( x ) = max ⁡ ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)

R e L U ReLU ReLU对输入 x x x进行非线性变换:
∙ 当  x > 0  时,ReLU ( x ) = x ∙ 当  x ≤ 0  时,ReLU ( x ) = 0 \bullet\quad\text{当 }x>0\text{ 时,ReLU}(x)=x\text{}\\\bullet\quad\text{当 }x\leq0\text{ 时,ReLU}(x)=0\text{}  x>0 ,ReLU(x)=x x0 ,ReLU(x)=0

3.2 特征

  1. 计算简单:ReLU 的计算非常简单,只需要对输入进行一次比较运算,这在实际应用中大大加速了神经网络的训练。

  2. ReLU 函数的导数是分段函数:
    ReLU ′ ( x ) = { 1 , if  x > 0 0 , if  x ≤ 0 \text{ReLU}'(x)=\begin{cases}1,&\text{if } x>0\\0,&\text{if }x\leq0\end{cases} ReLU(x)={1,0,if x>0if x0

  3. 缓解梯度消失问题:相比于 Sigmoid 和 Tanh 激活函数,ReLU 在正半区的导数恒为 1,这使得深度神经网络在训练过程中可以更好地传播梯度,不存在饱和问题。

  4. 稀疏激活:ReLU在输入小于等于 0 时输出为 0,这使得 ReLU 可以在神经网络中引入稀疏性(即一些神经元不被激活),这种稀疏性可以减少网络中的冗余信息,提高网络的效率和泛化能力。

3.3 缺点

神经元死亡:由于 R e L U ReLU ReLU x ≤ 0 x≤0 x0时输出为 0 0 0,如果某个神经元输入值是负,那么该神经元将永远不再激活,成为“死亡”神经元。随着训练的进行,网络中可能会出现大量死亡神经元,从而会降低模型的表达能力。

3.4 函数绘图

参考代码如下:

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 中文问题
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test006():
    # 输入数据x
    x = torch.linspace(-20, 20, 1000)
    y = F.relu(x)
    # 绘制一行2列
    _, ax = plt.subplots(1, 2)
    ax[0].plot(x.numpy(), y.numpy())
    # 显示坐标格子
    ax[0].grid()
    ax[0].set_title("relu 激活函数")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")

    # 绘制导数函数
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    F.relu(x).sum().backward()
    ax[1].plot(x.detach().numpy(), x.grad.numpy())
    ax[1].grid()
    ax[1].set_title("relu 激活函数导数", color="red")
    # 设置绘制线色颜色
    ax[1].lines[0].set_color("red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")

    plt.show()


if __name__ == "__main__":
    test006()

汇总结果:

在这里插入图片描述

4.LeakyReLU

Leaky ReLU是一种对 ReLU 函数的改进,旨在解决 ReLU 的一些缺点,特别是Dying ReLU 问题。Leaky ReLU 通过在输入为负时引入一个小的负斜率来改善这一问题。

4.1 公式

Leaky ReLU 函数的定义如下:
Leaky ReLU ( x ) = { x , if  x > 0 α x , if  x ≤ 0 \text{Leaky ReLU}(x)=\begin{cases}x,&\text{if } x>0\\\alpha x,&\text{if } x\leq0\end{cases} Leaky ReLU(x)={x,αx,if x>0if x0
其中, α \alpha α 是一个非常小的常数(如 0.01),它控制负半轴的斜率。这个常数 α \alpha α是一个超参数,可以在训练过程中可自行进行调整。

4.2 特征

  1. 避免神经元死亡:通过在 x ≤ 0 x\leq 0 x0 区域引入一个小的负斜率,这样即使输入值小于等于零,Leaky ReLU仍然会有梯度,允许神经元继续更新权重,避免神经元在训练过程中完全“死亡”的问题。
  2. 计算简单:Leaky ReLU 的计算与 ReLU 相似,只需简单的比较和线性运算,计算开销低。

4.3 缺点

  1. 参数选择: α \alpha α 是一个需要调整的超参数,选择合适的 α \alpha α 值可能需要实验和调优。
  2. 出现负激活:如果 α \alpha α 设定得不当,仍然可能导致激活值过低。

4.4 函数绘制

参考代码:

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 中文设置
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test006():
    x = torch.linspace(-5, 5, 200)
    # 设置leaky_relu的负斜率超参数
    slope = 0.03
    y = F.leaky_relu(x, slope)
    # 一行两列
    _, ax = plt.subplots(1, 2)
    # 开始绘制函数曲线图
    ax[0].plot(x, y)
    ax[0].set_title("Leaky ReLU 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    ax[0].grid(True)

    # 绘制leaky_relu的梯度曲线图
    x = torch.linspace(-5, 5, 200, requires_grad=True)
    F.leaky_relu(x, slope).sum().backward()
    ax[1].plot(x.detach().numpy(), x.grad)
    ax[1].set_title("Leaky ReLU 梯度曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")
    ax[1].grid(True)
    # 设置线的颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test006()

绘制结果:

在这里插入图片描述

5.softmax

Softmax激活函数通常用于分类问题的输出层,它能够将网络的输出转换为概率分布,使得输出的各个类别的概率之和为 1。Softmax 特别适合用于多分类问题。

5.1 公式

假设神经网络的输出层有 n n n个节点,每个节点的输入为 z i z_i zi,则 Softmax 函数的定义如下:
S o f t m a x ( z i ) = e z i ∑ j = 1 n e z j \mathrm{Softmax}(z_i)=\frac{e^{z_i}}{\sum_{j=1}^ne^{z_j}} Softmax(zi)=j=1nezjezi

给定输入向量 z = [ z 1 , z 2 , … , z n ] z=[z_1,z_2,…,z_n] z=[z1,z2,,zn]

1.指数变换:对每个 z i z_i zi进行指数变换,得到 t = [ e z 1 , e z 2 , . . . , e z n ] t = [e^{z_1},e^{z_2},...,e^{z_n}] t=[ez1,ez2,...,ezn],使z的取值区间从 ( − ∞ , + ∞ ) (-\infty,+\infty) (,+)变为 ( 0 , + ∞ ) (0,+\infty) (0,+)

2.将所有指数变换后的值求和,得到 s = e z 1 + e z 2 + . . . + e z n = Σ j = 1 n e z j s = e^{z_1} + e^{z_2} + ... + e^{z_n} = \Sigma_{j=1}^ne^{z_j} s=ez1+ez2+...+ezn=Σj=1nezj

3.将t中每个 e z i e^{z_i} ezi除以归一化因子s,得到概率分布:
s o f t m a x ( z ) = [ e z 1 s , e z 2 s , . . . , e z n s ] = [ e z 1 Σ j = 1 n e z j , e z 2 Σ j = 1 n e z j , . . . , e z n Σ j = 1 n e z j ] softmax(z) =[\frac{e^{z_1}}{s},\frac{e^{z_2}}{s},...,\frac{e^{z_n}}{s}]=[\frac{e^{z_1}}{\Sigma_{j=1}^ne^{z_j}},\frac{e^{z_2}}{\Sigma_{j=1}^ne^{z_j}},...,\frac{e^{z_n}}{\Sigma_{j=1}^ne^{z_j}}] softmax(z)=[sez1,sez2,...,sezn]=[Σj=1nezjez1,Σj=1nezjez2,...,Σj=1nezjezn]
即:
S o f t m a x ( z i ) = e z i ∑ j = 1 n e z j \mathrm{Softmax}(z_i)=\frac{e^{z_i}}{\sum_{j=1}^ne^{z_j}} Softmax(zi)=j=1nezjezi
从上述公式可以看出:

  1. 每个输出值在 (0,1)之间

  2. Softmax()对向量的值做了改变,但其位置不变

  3. 所有输出值之和为1,即

s u m ( s o f t m a x ( z ) ) = e z 1 s + e z 2 s + . . . + e z n s = s s = 1 sum(softmax(z)) =\frac{e^{z_1}}{s}+\frac{e^{z_2}}{s}+...+\frac{e^{z_n}}{s}=\frac{s}{s}=1 sum(softmax(z))=sez1+sez2+...+sezn=ss=1

5.2 特征

  1. 将输出转化为概率:通过 S o f t m a x Softmax Softmax,可以将网络的原始输出转化为各个类别的概率,从而可以根据这些概率进行分类决策。
    在这里插入图片描述

  2. 概率分布: S o f t m a x Softmax Softmax的输出是一个概率分布,即每个输出值 Softmax ( z i ) \text{Softmax}(z_i) Softmax(zi)都是一个介于 0 0 0 1 1 1之间的数,并且所有输出值的和为 1:
    ∑ i = 1 n Softmax ( z i ) = 1 \sum_{i=1}^n\text{Softmax}(z_i)=1 i=1nSoftmax(zi)=1

  3. 突出差异: S o f t m a x Softmax Softmax会放大差异,使得概率最大的类别的输出值更接近 1 1 1,而其他类别更接近 0 0 0

  4. 在实际应用中, S o f t m a x Softmax Softmax常与交叉熵损失函数Cross-Entropy Loss结合使用,用于多分类问题。在反向传播中, S o f t m a x Softmax Softmax的导数计算是必需的。

设  p i = S o f t m a x ( z i ) ,则对于  z i  的导数为: ∙  当  i = j  时: ∂ p i ∂ z i = e z i ( Σ j = 1 n e z j ) − e z i e z i ( Σ j = 1 n e z j ) 2 = p i ( 1 − p i ) ∙  当  i ≠ j  时 : ∂ p i ∂ z j = 0 ( Σ j = 1 n e z j ) − e z i e z j ( Σ j = 1 n e z j ) 2 = − p i p j \begin{aligned} &\text{设 }p_i=\mathrm{Softmax}(z_i)\text{,则对于 }z_i\text{ 的导数为:} \\ &\bullet\text{ 当 }i=j\text{ 时:} \\ &&&\frac{\partial p_i}{\partial z_i}=\frac{e^{z_i}(\Sigma_{j=1}^ne^{z_j})-e^{z_i}e^{z_i}}{(\Sigma_{j=1}^ne^{z_j})^2}=p_i(1-p_i) \\ & \bullet\text{ 当 }i\neq j\text{ 时}: \\ &&&\frac{\partial p_i}{\partial z_j}=\frac{0(\Sigma_{j=1}^ne^{z_j})-e^{z_i}e^{z_j}}{(\Sigma_{j=1}^ne^{z_j})^2} =-p_{i}p_{j} \end{aligned}  pi=Softmax(zi),则对于 zi 的导数为:  i=j :  i=j :zipi=(Σj=1nezj)2ezi(Σj=1nezj)eziezi=pi(1pi)zjpi=(Σj=1nezj)20(Σj=1nezj)eziezj=pipj

5.3 缺点

  1. 数值不稳定性:在计算过程中,如果 z i z_i zi的数值过大, e z i e^{z_i} ezi可能会导致数值溢出。因此在实际应用中,经常会对 z i z_i zi进行调整,如减去最大值以确保数值稳定。

S o f t m a x ( z i ) = e z i − max ⁡ ( z ) ∑ j = 1 n e z j − max ⁡ ( z ) \mathrm{Softmax}(z_i)=\frac{e^{z_i-\max(z)}}{\sum_{j=1}^ne^{z_j-\max(z)}} Softmax(zi)=j=1nezjmax(z)ezimax(z)

解释:

z i − max ⁡ ( z ) z_i-\max(z) zimax(z)是一个非正数,由于 e z i − m a x ( z ) e^{z_i−max(z)} ezimax(z) 的形式,当 z i z_i zi 接近 max(z) 时, e z i − m a x ( z ) e^{z_i−max(z)} ezimax(z) 的值会接近 1,而当 z i z_i zi 远小于 max(z) 时, e z i − m a x ( z ) e^{z_i−max(z)} ezimax(z) 的值会接近 0。这使得 Softmax 函数的输出中,最大值对应的概率会相对较大,而其他值对应的概率会相对较小,从而提高数值稳定性。

这种调整不会改变 S o f t m a x Softmax Softmax的概率分布结果,因为从数学的角度讲相当于分子、分母都除以了 e max ⁡ ( z ) e^{\max(z)} emax(z)

在 PyTorch 中,torch.nn.functional.softmax 函数就自动处理了数值稳定性问题。

  1. 难以处理大量类别: S o f t m a x Softmax Softmax在处理类别数非常多的情况下(如大模型中的词汇表)计算开销会较大。

5.4 代码实现

代码参考如下:

import torch
import torch.nn as nn

# 表示4分类,每个样本全连接后得到4个得分,下面示例模拟的是两个样本的得分
input_tensor = torch.tensor([[-1.0, 2.0, -3.0, 4.0], [-2, 3, -3, 9]])

softmax = nn.Softmax()
output_tensor = softmax(input_tensor)
# 关闭科学计数法
torch.set_printoptions(sci_mode=False)
print("输入张量:", input_tensor)
print("输出张量:", output_tensor)

输出结果:

输入张量: tensor([[-1.,  2., -3.,  4.],
        [-2.,  3., -3.,  9.]])
输出张量: tensor([[    0.0059,     0.1184,     0.0008,     0.8749],
        [    0.0000,     0.0025,     0.0000,     0.9975]])

如何选择

更多激活函数可以查看官方文档:https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity
那这么多激活函数应该如何选择呢?实际没那么纠结

1.隐藏层

  1. 优先选ReLU;
  2. 如果ReLU效果不咋地,那么尝试其他激活,如Leaky ReLU等;
  3. 使用ReLU时注意神经元死亡问题, 避免出现过多神经元死亡;
  4. 避免使用sigmoid,尝试使用tanh;

2.输出层

  1. 二分类问题选择sigmoid激活函数;
  2. 多分类问题选择softmax激活函数;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值