《PyTorch面试精华》


1 前言
    1.1  PyTorch安装

    1.2  显卡驱动的困惑

    1.3  CUDA安装注意事项

    1.4  cuDNN的介绍

    1.5  Pytorch Lightning介绍

    1.6  PyTorch学习之道

    1.7  PyTorch快速入门

    1.8  PyTorch调参之道

    1.9  PyTorch调参套件

    1.10  手动创建虚拟环境

2 深度学习之数学基础
    2.1  希腊字母解读

    2.2  梯度的物理意义

    2.3  图解梯度下降法

    2.4  图解梯度上升法

    2.5  自然梯度

    2.6  泰勒公式的介绍

    2.7  信息与信息熵

    2.8  重要性采样

    2.10  欧几里得范数

    2.11  特征值和特征向量

    2.12  似然函数的理解

    2.13  矩阵秩的深刻理解

3 PyTorch入门疑难点
4 PyTorch全局设置
    4.1  全局设置当前设备

    4.2  全局设置浮点精度

5 PyTorch GPU分布式训练
    5.1  PyTorch GPU基础操作

    5.2  DataParallel用法详解

    5.3  GPU分布式训练模型

    5.4  CUDA_VISIBLE_DEVICES

    5.5  device详细说明

    5.6  to(device)和.cuda()

    5.7  CUDA设备索引

    5.8  GPU设备索引

6 向量的基础与核心
    6.1  Tensor的组成与存储

    6.2  Tensor的grad属性

    6.4  Tensor的叠加

    6.5  禁用梯度计算

    6.6  向量的保存和加载

    6.7  参数向量

    6.8  叶子节点

    6.9  detach原理

    6.10  requires_grad属性

    6.11  Tensor与Numpy互换

    6.12  张量cat操作

    6.13  零维张量

    6.15  squeeze/unsqu...函数

    6.16  argmax和max的区别

    6.17  torch.as_tensor的应用

7 神经网络基础
    7.2  PyTorch计算图

    7.3  查看网络权重参数

    7.4  保存模型

    7.5  Adam相关面试题

    7.6  Train模式和Eval模式

    7.7  线性网络

    7.8  双线性网络

    7.9  惰性线性层

    7.10  PyTorch中的自动微分

    7.12  Dropout机制

    7.13  半精度训练

    7.14  Xavier初始化

    7.15  注意力机制

    7.16  Dataset数据处理

    7.17  StepLR学习率调度器

    7.18  词嵌入的理解

    7.19  TensorDataset的使用

    7.20  模型的保存与加载

    7.21  ModuleList和Sequential

    7.22  Batch Normalization介绍

8 计算机视觉基础知识
    8.1  通道的深刻理解

    8.2  1x1卷积的作用

    8.3  特征提取和可视化

    8.4  反卷积的推导

    8.5  理解卷积

    8.7  空洞卷积

    8.8  池化层的作用

    8.9  感受野与特征图

    8.10  NMS算法

    8.11  特征图尺寸计算

9 循环神经网络基础
    9.2  RNN的介绍

10 注意力机制
    10.1  位置编码的作用

    10.2  位置编码的种类

    10.4  Embedding本质理解

    10.6  Transformer VS CNN/RNN

    10.7  ELMo介绍

11 PyTorch归一化
    11.2  层归一化技术详解

12 激活函数相关内容
    12.1  激活函数简介

    12.2  万能逼近定理

    12.3  指数函数的学习

    12.4  Sigmoid函数的介绍

    12.5  Tanh函数的介绍

    12.6  Softmax函数的实现

    12.7  ReLU函数的介绍

    12.8  Leaky Relu函数的介绍

    12.9  ReLu与非线性的理解

    12.10  Parametric ReLU函数

    12.11  ELU函数介绍

    12.12  神经元死亡的问题

13 思考题的答案
    13.1  思考题的答案解密

ELU函数介绍

创建时间:2024-11-25 更新时间:2025-09-07 阅读次数:1688 次

1、ELU函数的简介

ELU函数的全称是:Exponential Linear Units (ELUs),即指数线性单位。

2、ELU函数的定义

$ ELU(x) = \left \{ \begin{array}{ll} x,& if\;x>0 \\ \alpha*(e^x-1),& if\;x \leq 0 \end{array} \right.$

其中:$x$ 是输入值,$α$ 是一个超参数(默认为 1.0),控制负输入区域的饱和下限。

3、ELU函数的作用与优势

(1)缓解“死亡ReLU”问题

ReLU存在固有缺陷,当输入为负时,ReLU的输出恒为0,可能导致神经元“死亡”(无法更新梯度)。而ELU针对这个问题进行了改进,在负输入区域,ELU使用指数函数平滑地过渡到负值(下限为-$α$),使得梯度非零,有助于梯度传播和参数更新。

(2)输出均值接近零

ELU函数在负区域的值域为 (-$α$, $0$) ,而正区域保持线性。这种对称性使得输出的均值更接近零(类似Batch Normalization的效果),可能加速训练收敛。

(3)平滑性

ELU函数在$x=0$处可导(且导数连续),避免了ReLU在零点处的不可导问题(实际应用中通常忽略ReLU的零点不可导)。

4、ELU函数的使用

在PyTorch中有专门的ELU函数,其语法形式如下所示:

torch.nn.ELU(alpha=1.0, inplace=False)

代码举例:

import torch
import torch.nn as nn

# 方式1:作为网络层使用
elu_layer = nn.ELU(alpha=1.0)  # alpha 默认为1.0
input_tensor = torch.tensor([-1.0, 0.0, 2.0])
output = elu_layer(input_tensor)  # 输出: [-0.6321, 0.0, 2.0]

# 方式2:函数式调用
output = torch.nn.functional.elu(input_tensor, alpha=1.0)

5、ELU函数输出均值接近零的展示代码

ELU函数有个重要的特性:输出均值接近零,下面我们来演示一下。

首先我们生成一些随机数据(比如符合正态分布的数据),然后通过ELU激活函数后观察其输出的均值是否接近零。由于ELU在负值区域使用指数函数,它能够将负值推向一个负的饱和值,从而使得输出的均值接近零。

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei"]  # 用来正常显示中文标签
plt.rcParams["axes.unicode_minus"] = False  # 用来正常显示负号

# 创建ELU激活函数
elu = nn.ELU(alpha=1.0)  # alpha参数默认为1.0

# 生成不同分布的输入数据
torch.manual_seed(42)  # 设置随机种子以确保可重复性

# 创建不同均值的正态分布数据
means = [-2.0, -1.0, 0.0, 1.0, 2.0]
std_dev = 1.0
num_samples = 10000

# 存储结果
results = []

plt.figure(figsize=(15, 10))

for i, mean in enumerate(means):
    # 生成输入数据
    input_data = torch.normal(mean=mean, std=std_dev, size=(num_samples,))

    # 应用ELU激活函数
    output_data = elu(input_data)

    # 计算统计量
    input_mean = input_data.mean().item()
    input_std = input_data.std().item()
    output_mean = output_data.mean().item()
    output_std = output_data.std().item()

    results.append({
        "input_mean": input_mean,
        "input_std": input_std,
        "output_mean": output_mean,
        "output_std": output_std
    })

    # 绘制输入和输出的分布
    plt.subplot(2, 3, i + 1)
    plt.hist(input_data.numpy(), bins=50, alpha=0.5, label=f"输入 (μ={input_mean:.2f})", color="blue")
    plt.hist(output_data.numpy(), bins=50, alpha=0.5, label=f"ELU输出 (μ={output_mean:.2f})", color="red")
    plt.title(f"输入均值 = {mean}")
    plt.legend()
    plt.xlabel("值")
    plt.ylabel("频率")

plt.tight_layout()
plt.suptitle("ELU激活函数对不同均值输入的输出分布", fontsize=16, y=1.02)
plt.show()

# 打印统计结果
print("ELU激活函数的输出均值接近零的统计结果:")
print("=" * 60)
for i, (mean, result) in enumerate(zip(means, results)):
    print(f"输入均值: {mean:.1f} -> 输出均值: {result["output_mean"]:.4f} "
          f"(偏移: {abs(result["output_mean"]):.4f})")

这段代码展示了ELU激活函数如何使输出均值接近零的特性。代码中创建了均值为 $-2, -1, 0, 1, 2$ 的正态分布数据,然后对每种输入数据应用 ELU 激活函数,并计算输出的均值。最后的效果如下图所示:

6、ELU与ReLU对负均值输入的对比

为了让大家更明确地看懂ELU与ReLU的区别,我特意做了一个图示例子,如下所示:

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei"]  # 用来正常显示中文标签
plt.rcParams["axes.unicode_minus"] = False  # 用来正常显示负号

# 创建ELU激活函数
elu = nn.ELU(alpha=1.0)  # alpha参数默认为1.0

# 生成不同分布的输入数据
torch.manual_seed(42)  # 设置随机种子以确保可重复性

# 创建不同均值的正态分布数据
means = [-2.0, -1.0, 0.0, 1.0, 2.0]
std_dev = 1.0
num_samples = 10000

# 与ReLU进行对比
relu = nn.ReLU()

# 测试ELU和ReLU对负均值的处理
neg_mean_input = torch.normal(mean=-1.5, std=1.0, size=(num_samples,))

elu_output = elu(neg_mean_input)
relu_output = relu(neg_mean_input)

print("\nELU与ReLU对负均值输入的对比:")
print("=" * 50)
print(f"输入均值: {neg_mean_input.mean().item():.4f}")
print(f"ELU输出均值: {elu_output.mean().item():.4f}")
print(f"ReLU输出均值: {relu_output.mean().item():.4f}")

# 可视化对比
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.hist(neg_mean_input.numpy(), bins=50, alpha=0.5, label="输入", color="gray")
plt.hist(elu_output.numpy(), bins=50, alpha=0.5, label="ELU输出", color="red")
plt.title("ELU对负均值输入的处理")
plt.xlabel("值")
plt.ylabel("频率")
plt.legend()

plt.subplot(1, 2, 2)
plt.hist(neg_mean_input.numpy(), bins=50, alpha=0.5, label="输入", color="gray")
plt.hist(relu_output.numpy(), bins=50, alpha=0.5, label="ReLU输出", color="blue")
plt.title("ReLU对负均值输入的处理")
plt.xlabel("值")
plt.ylabel("频率")
plt.legend()

plt.tight_layout()
plt.show()

下图可以很明显的看出ELU对负均值输入的处理更加合理:

7、ELU函数的适用场景

(1)希望避免“死亡ReLU”问题且对计算开销不敏感的任务。

(2)需要输出均值接近零的模型(如无需 Batch Normalization 的简化网络)。

8、使用ELU函数的注意事项

计算成本:ELU 的指数计算($e^x$)比 ReLU 更耗时,可能影响训练/推理速度。

超参数选择:$α$ 通常保持默认值 1.0,但也可根据任务调整。ELU在不同alpha参数下的表现,如下图所示:

9、ELU函数的发现史

ELU 函数 是由 Djork-Arné Clevert、Thomas Unterthiner 和 Sepp Hochreiter 于 2015 年发表的论文中首次提出的。作者首先分析了当时最流行的激活函数 ReLU 的局限性。虽然 ReLU 成功解决了梯度消失问题并加速了训练,但它也存在所谓的“死亡 ReLU”问题(Dying ReLU Problem),即一旦神经元输出为负,其梯度将永远为0,该神经元在后续训练中可能再也无法被激活。为了克服 ReLU 的缺点,他们提出了 ELU 函数。对于正输入,其行为与 ReLU 完全一样,对于负输入,它不是一个简单的零或小的斜率(如 Leaky ReLU 或 PReLU),而是一个平滑的、渐近饱和的指数函数。平滑饱和的意义在于使得ELU的输出均值更接近零,这被认为可以加速学习,因为它让每一层的输入更接近以零为中心分布,类似于批归一化(Batch Normalization)的效果。

实验结果:论文在多个图像分类(如 CIFAR-10, CIFAR-100, ImageNet)和语音处理数据集上进行了实验,结果表明:(1)使用 ELU 函数的神经网络在分类准确率上通常优于或与使用 ReLU、LReLU 等激活函数的网络相当。(2)ELU 能够带来更快的收敛速度,有时甚至无需批归一化的辅助。

本教程共117节,当前为第115节!
本教程最新修订时间为:2026-05-08 11:10:53