手撕论文知识库

手撕论文知识库

深度学习项目代码目录全览及解析

常见目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
project_name/
├── data/ # 数据集相关(原始/处理后的数据)
├── dataloader/ # 数据加载与预处理模块(核心)
│ ├── __init__.py
│ ├── dataset.py # 自定义Dataset类
│ └── transforms.py # 数据增强操作
├── models/ # 模型定义(核心)
│ ├── __init__.py
│ ├── backbone.py # 主干网络
│ └── layers.py # 自定义网络层
├── configs/ # 超参数配置文件(如YAML/JSON)
├── utils/ # 工具函数(如日志、评估指标)
│ ├── data_utils.py
│ ├── model_utils.py
│ ├── visualization_utils.py
│ └──...
├── logs/ # 记录训练和评估过程中的日志信息
│ ├── training.log
│ ├── validation.log
│ └──...
├── checkpoints/ # 训练保存的模型权重
├── scripts/ # 运行脚本(训练/测试命令)
├── requirements.txt # 依赖库列表
├── environment.yml # Conda环境配置
├── README.md # 项目说明
└── main.py # 主程序入口
  1. dataloader/

作用:数据加载、预处理、增强(如论文项目中的 with_colmap.py 可能与多视图数据对齐相关)

典型内容Dataset 类定义、数据增强函数、特征提取工具(如项目中的 with_feature.py

  1. models/

作用:定义神经网络模型(如项目中的nerf_models.py 可实现NeRF的核心架构)。

典型内容:模型类继承torch.nn.Module,包含前向传播逻辑(如项目中的depth_decoder.py可用于深度估计解码)。

  1. utils/

作用:辅助工具(如项目中的 pose_utils.py 处理相机位姿,training_utils.py 封装训练逻辑)。

典型内容:评估指标计算、可视化工具、训练回调函数。

  1. third_party/

作用:第三方库或工具(如项目中的 ATE 可能用于轨迹评估,pytorch_ssim 实现结构相似性损失)。

  1. 其他关键要素

logs 文件夹:记录训练和评估过程中的日志信息,如训练损失、验证损失、准确率等指标的变化情况,便于跟踪模型训练过程,分析模型的收敛性和性能表现。

checkpoints 文件夹:保存训练过程中的模型检查点,即模型在不同训练阶段的参数文件,用于在训练中断时恢复训练,或者用于选择在验证集上表现最好的模型进行测试和部署。

requirement.txt:列出项目所需的 Python 依赖库及其版本号,便于在新环境中快速安装项目所需的所有依赖。

environment.yml:Conda环境配置,确保依赖一致性。

README.md:项目说明、安装与使用指南(深度学习项目必备)。

main.py:项目的主程序入口,通常包含模型训练、评估和预测的主要逻辑,可通过命令行参数来控制程序的运行方式和参数设置。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
occ - nerf/  # 基于占用率的神经辐射场项目根目录
├──.gitignore # 指定Git不跟踪的文件或文件夹
├── LICENSE # 项目使用许可条款文件
├── README.md # 介绍项目背景、功能、使用方法等的说明文档
├── environment.yml # 定义项目运行所需软件环境
├── local1.txt # 用途不明,或为本地说明、配置等文件
├── dataloader/ # 存放数据加载与预处理代码
│ ├── any_folder.py # 从任意文件夹结构加载数据
│ ├── local_save.py # 负责数据本地保存
│ ├── with_colmap.py # 从COLMAP处理后格式加载数据
│ ├── with_feature.py # 加载带特征的数据
│ ├── with_feature_colmap.py # 结合COLMAP与特征数据加载
│ └── with_mask.py # 加载带掩码的数据
├── models/ # 存放深度学习模型定义与操作代码
│ ├── depth_decoder.py # 深度解码,用于深度估计
│ ├── intrinsics.py # 处理相机内参相关内容
│ ├── layers.py # 定义深度学习层结构
│ ├── nerf_feature.py # 处理NeRF特征相关逻辑
│ ├── nerf_mask.py # 处理NeRF模型中掩码相关内容
│ ├── nerf_models.py # 定义NeRF模型架构等核心内容
│ └── poses.py # 处理位姿相关操作
├── utils/ # 包含辅助项目运行的工具函数
│ ├── align_traj.py # 实现轨迹对齐算法
│ ├── comp_ate.py # 计算绝对轨迹误差
│ ├── comp_ray_dir.py # 计算光线方向
│ ├── lie_group_helper.py # 提供李群相关辅助函数
│ ├── pos_enc.py # 实现位置编码
│ ├── pose_utils.py # 提供位姿相关实用工具函数
│ ├── split_dataset.py # 划分数据集为训练、验证、测试集
│ ├── training_utils.py # 提供模型训练辅助函数
│ ├── vgg.py # 与VGG神经网络相关操作
│ ├── vis_cam_traj.py # 可视化相机轨迹
│ └── volume_op.py # 操作三维体数据
├── tasks/ # 存放训练、测试等具体任务代码
│ └──...
└── third_party/ # 存放第三方代码或库
├── ATE/ # 与绝对轨迹误差计算相关
│ └── README.md # 说明该部分功能与用法
└── pytorch_ssim/ # 计算结构相似性指数的库

深度学习/神经网络架构总览

PyTorch

什么是torch

Torch 是 PyTorch 深度学习框架的核心库,具备强大的功能与广泛的用途。它提供了丰富的张量操作,可在 CPU 或 GPU 上高效计算,能轻松处理各类数据;其自动求导机制极大简化了深度学习中梯度计算与反向传播的过程,让模型训练更为便捷。借助torch.nn模块可方便构建如 CNN、RNN 等复杂神经网络架构,torch.optim模块提供多种优化算法用于模型参数更新。此外,Torch 还支持预训练模型的使用与微调,结合可视化工具能助力监控训练过程,广泛应用于图像、自然语言处理、推荐系统等诸多领域。

torch常用函数和功能

张量

什么是张量

张量是多维数组的泛化表示,可理解为一个多维的数据容器,零维张量是标量,一维张量是向量,二维张量是矩阵,三维及以上则是更高阶的张量。在深度学习里,使用张量是因为它能够高效地表示和处理大量的数据,像图像可表示为三维张量(高度、宽度、通道数),视频可表示为四维张量(帧数、高度、宽度、通道数)。并且,深度学习框架(如 PyTorch)针对张量运算进行了高度优化,能利用 GPU 等硬件加速计算,张量还能自然地支持自动求导机制,方便进行模型训练时的梯度计算和参数更新。

张量是 PyTorch 中最基础的数据结构,类似于 NumPy 的多维数组,但它可以在 GPU 上进行加速计算,并且支持自动求导等深度学习所需的特性。

张量的维度

维度(也称为轴)是指张量在某个方向上的延伸。可以将维度理解为数据组织的一个方向或一个层次,类似于在地理坐标系统中,经度和纬度分别代表了不同的方向,张量的每个维度也代表了数据的一个特定方向的排列。维度的数量被称为张量的阶(rank),零阶张量是标量(一个单独的数值),一阶张量是向量(一维数组),二阶张量是矩阵(二维数组),三阶及以上的张量则用于表示更复杂的数据结构。

注:维度从0开始算起,比如对于二阶张量,维度0代表行,维度1代表列

另:维度排列遵循($a_n$, $a_{n-1}$, …, $a_1$)的形式,数字越前代表越高维的堆叠。比如torch.zeros((2, 3, 4, 5)),那就是两个三维(三层)的4x5矩阵叠在一起

1.零阶张量(标量)

零阶张量只有一个数值,它没有方向的概念,维度数量为 0。例如这里的 scalar 就是一个零阶张量,它代表一个单一的数值,不涉及方向或多个元素的排列。

1
2
3
import torch
scalar = torch.tensor(5)
print("标量的维度数量:", scalar.dim())

2.一阶张量(向量)

一阶张量可以看作是一个向量,它有一个维度。这个维度代表了向量中元素的排列方向,向量的长度就是这个维度的大小。例如vector 是一个一阶张量,维度数量为 1,该维度的大小为 4,表示向量中有 4 个元素。

1
2
3
vector = torch.tensor([1, 2, 3, 4])
print("向量的维度数量:", vector.dim())
print("向量在该维度的大小:", vector.size(0))

3.二阶张量(矩阵)

二阶张量是一个矩阵,有两个维度,通常称为行和列。第一个维度代表矩阵的行方向,第二个维度代表矩阵的列方向。例如matrix 是一个 2 行 3 列的矩阵,第一个维度的大小为 2 表示有 2 行,第二个维度的大小为 3 表示有 3 列。

1
2
3
4
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]])
print("矩阵的维度数量:", matrix.dim())
print("矩阵第一个维度(行)的大小:", matrix.size(0))
print("矩阵第二个维度(列)的大小:", matrix.size(1))

4.高阶张量(图像、视频等)

对于三阶及以上的张量,维度的含义更加丰富,通常与具体的数据类型和应用场景相关。

图像数据:在处理图像时,通常使用三阶张量。例如,一张彩色图像可以表示为一个形状为 (高度, 宽度, 通道数) 的三阶张量。这里的第一个维度代表图像的高度方向,第二个维度代表图像的宽度方向,第三个维度代表图像的通道(如 RGB 三个通道)。

image = torch.randn(224, 224, 3) 这行代码能够随机生成一个形状为 (224, 224, 3) 的张量来模拟图像的三通道数值。

由于 torch.randn() 生成的是服从标准正态分布的随机数,这些数值可能为负数,也可能超出了常见图像像素值的范围(通常是 0 - 255 或者 0 - 1)。在实际的图像处理任务中,如果需要模拟真实图像,可能需要对这些随机值进行进一步的处理,例如通过归一化或裁剪操作将其限制在合适的范围内。

1
2
3
4
5
image = torch.randn(224, 224, 3)
print("图像张量的维度数量:", image.dim())
print("图像高度维度的大小:", image.size(0))
print("图像宽度维度的大小:", image.size(1))
print("图像通道维度的大小:", image.size(2))

image-20250219032451598

视频数据:视频可以看作是一系列的图像帧,因此可以用四阶张量表示,形状通常为 (帧数, 高度, 宽度, 通道数)。第一个维度代表视频中的帧数,其余维度与图像张量的含义相同。

*5.通道

通道指图像中特定类型信息的集合,图像可含一个或多个通道,各通道存储图像某方面特征数据。像单通道存亮度,RGB 三通道分别存红、绿、蓝颜色信息,四通道还多了透明度通道,以此组合完整呈现图像。

通道能实现颜色表示与混合,如 RGB 三通道通过不同数值组合呈现丰富色彩;可用于特征提取与分析,不同通道提供不同特征,助力图像分析和目标识别;还能用于图像合成与特效制作,借助透明度通道可控制图像透明效果实现合成。

在图片里,灰度图用单通道呈现黑白影像;彩色照片靠 RGB 三通道展示多彩画面;PNG 图片利用四通道含透明度信息实现图像融合。视频是连续的图片帧,同样利用通道来呈现色彩、进行特效处理,如影视中常见的抠图合成场景就借助了通道特性。

张量相关函数

1.创建

  • 创建张量torch.tensor()

    1
    2
    3
    import torch
    data = [1, 2, 3]
    tensor = torch.tensor(data)

当你有现有的数据存储在 Python 列表或 NumPy 数组中,并且需要将其输入到 PyTorch 模型进行计算时使用。例如,在加载数据集后,将数据转换为张量形式以便后续处理。

从数据存储的角度来看,tensor 存储了 Python 列表 data 中的元素 [1, 2, 3]。它将这些数据以一种高效的、适合计算机处理的方式组织起来,存储在内存中。在这个例子中,tensor 是一个一维张量,形状为 (3,),这意味着它包含 3 个元素。

在数学运算方面,tensor 可以参与各种数学运算,如加法、乘法、矩阵乘法等。PyTorch 为张量提供了丰富的数学运算函数,这些运算可以在 CPU 或 GPU 上高效执行。

在深度学习的上下文中,tensor 是模型输入、输出以及参数的基本表示形式。例如,在一个简单的全连接神经网络中,输入数据会被转换为张量输入到网络中,网络的权重和偏置也是以张量的形式存储和更新的。在上述例子中,tensor 可以作为一个简单的输入数据示例,如果要构建一个神经网络处理这个输入,可能会进行如下操作(以下是一个简单示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
import torch.nn as nn

# 定义一个简单的全连接层
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc = nn.Linear(3, 1) # 输入维度为 3,输出维度为 1

def forward(self, x):
return self.fc(x)

# 创建模型实例
model = SimpleNet()

# 准备输入数据
data = [1, 2, 3]
tensor = torch.tensor(data, dtype=torch.float32).unsqueeze(0) # 转换为适合输入模型的形状

# 进行前向传播
output = model(tensor)
print("模型输出:", output)
  • 创建全零张量torch.zeros()

    1
    zeros_tensor = torch.zeros((2, 3))

常用于初始化某些变量,如在初始化神经网络的偏置项时,可使用全零张量。另外,在需要填充零值进行数据预处理或占位时也会用到。

1
2
3
4
创建的全零张量:
tensor([[0., 0., 0.],
[0., 0., 0.]])
张量的形状: torch.Size([2, 3])

传入的参数 (2, 3)对应创建的 zeros_tensor 是一个 2 行 3 列的二维张量。如果使用 torch.zeros((2, 3, 4)) 这样的代码,那么创建的就是一个三维张量,其中 2 表示最外层维度的大小(可以想象成有 2 个二维矩阵堆叠在一起),3 表示每个二维矩阵的行数,4 表示每个二维矩阵的列数。依此类推,对于更高维的张量,每个数字都代表对应维度上的大小。相当于高是2,行是3,列是4

1
2
3
4
5
6
7
8
9
创建的全零张量:
tensor([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],

[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
张量的形状: torch.Size([2, 3, 4])

如果是torch.zeros((2, 3, 4, 5)),那就是两个三维(三层)的4x5矩阵叠在一起,数字越前就代表越高维的堆叠。

  • 创建全一张量**torch.ones()**

    1
    ones_tensor = torch.ones((2, 3))

torch.zeros() 类似,可用于初始化特定变量。在一些归一化操作或需要特定初始值为 1 的场景中会使用。

  • 创建指定形状的随机张量,元素值在[0 , 1)之间**torch.rand()**

    1
    random_tensor = torch.rand((2, 3))

在初始化神经网络的权重时,随机初始化是常见的做法,可使用 torch.rand() 生成初始权重张量。

2.操作

  • torch.cat():用于在指定维度上拼接多个张量。

    1
    2
    3
    a = torch.tensor([[1, 2], [3, 4]])
    b = torch.tensor([[5, 6], [7, 8]])
    c = torch.cat((a, b), dim=1)

    当需要将多个张量合并为一个更大的张量时使用。例如,在处理多模态数据时,将不同模态的特征张量拼接在一起。

    torch.cat((a, b), dim=1) 表示在维度 1(列方向)上对张量 ab 进行拼接。可以看到,拼接后的张量 c 是将 ab 的列进行了合并,行数不变,列数变为原来两个张量列数之和。在处理多模态数据时,比如一个模态的数据特征用张量 a 表示,另一个模态的数据特征用张量 b 表示,通过这种拼接操作可以将不同模态的特征合并在一起,方便后续的处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    张量 a:
    tensor([[1, 2],
    [3, 4]])
    张量 b:
    tensor([[5, 6],
    [7, 8]])
    拼接后的张量 c:
    tensor([[1, 2, 5, 6],
    [3, 4, 7, 8]])

    在二维张量的语境下,维度 0 代表行方向。torch.cat((a, b), dim=0) 会将张量 b 按行的顺序拼接到张量 a 的下方,拼接后的张量列数不变,行数为原来两个张量行数之和。在实际应用中,若 ab 分别表示两组样本数据,在维度 0 上拼接就相当于将这两组样本合并成一组更大的样本集。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    张量 a:
    tensor([[1, 2],
    [3, 4]])
    张量 b:
    tensor([[5, 6],
    [7, 8]])
    在维度 0 上拼接后的张量 c:
    tensor([[1, 2],
    [3, 4],
    [5, 6],
    [7, 8]])
  • torch.reshape():改变张量的形状。

    1
    2
    x = torch.tensor([1, 2, 3, 4])
    reshaped_x = torch.reshape(x, (2, 2))

    在神经网络中,不同层之间的数据形状可能需要进行调整,使用 torch.reshape() 可以方便地改变张量形状以满足层的输入要求。

    torch.reshape(x, (2, 2)) 是将一维张量 x 重塑为二维张量 reshaped_x,形状为 (2, 2)。在神经网络中,不同层之间的数据形状可能不匹配,例如某一层的输出是一维向量,而后续层需要二维矩阵作为输入,这时就可以使用 torch.reshape() 来调整数据的形状,使其满足层的输入要求。

    1
    2
    3
    4
    5
    原始张量 x:
    tensor([1, 2, 3, 4])
    重塑后的张量 reshaped_x:
    tensor([[1, 2],
    [3, 4]])
  • torch.transpose():交换张量的两个维度。

    1
    2
    x = torch.tensor([[1, 2], [3, 4]])
    transposed_x = torch.transpose(x, 0, 1)

    在矩阵运算中,有时需要对矩阵进行转置操作。在图像处理中,可能需要调整图像张量的维度顺序。

    torch.transpose(x, 0, 1) 表示交换张量 x 的第 0 维和第 1 维。在这个二维矩阵的例子中,就是对矩阵进行了转置操作,原来的行变成了列,列变成了行。在矩阵运算中,矩阵转置是一个常见的操作,例如在计算矩阵乘法时可能需要对矩阵进行转置。在图像处理中,图像张量的维度顺序可能需要调整,比如将 (高度, 宽度, 通道数) 调整为 (通道数, 高度, 宽度) 以适应某些模型的输入要求,这时就可以使用 torch.transpose() 来实现。

    1
    2
    3
    4
    5
    6
    原始张量 x:
    tensor([[1, 2],
    [3, 4]])
    转置后的张量 transposed_x:
    tensor([[1, 3],
    [2, 4]])
  • torch.squeeze():移除张量中所有维度为 1 的轴(或指定轴)。

    1
    2
    x = torch.tensor([[[1], [2], [3]]])  # 形状 [1, 3, 1]
    y = torch.squeeze(x) # 形状变为 [3]

    在张量操作中,某些操作(如池化、索引)可能产生冗余的维度为 1 的轴。例如:

    • 处理单通道图像时,通道维度可能为 1。
    • 批量处理单个样本时,批量维度可能为 1。
    • 某些神经网络层的输出可能保留不必要的单维度。

    torch.squeeze() 默认移除所有大小为 1 的维度,也可指定 dim 参数移除特定轴(仅当该轴大小为 1 时生效)。

    1
    2
    3
    4
    5
    6
    7
    8
    输入张量 x:
    tensor([[[1],
    [2],
    [3]]])
    输出张量 y:
    tensor([1, 2, 3])

    squeeze() 移除了所有大小为 1 的维度(第 0 维和第 2 维),仅保留第 1 维(大小为 3)。

自动求导

前向传播(Forward Propagation)
  1. 定义

前向传播是深度学习模型处理输入数据以产生输出的过程。在这个过程中,输入数据从输入层开始,依次经过神经网络的各个隐藏层,每层都会对输入进行特定的数学变换(如加权求和后通过激活函数),最终到达输出层得到预测结果。可以将其看作是信息从输入向输出流动的过程,每一层根据前一层的输出计算本层的输出,逐步传递直至得到最终输出。

  1. 作用

根据当前模型的参数(权重和偏置)对输入数据进行预测。通过一系列的线性和非线性变换,模型能够学习到输入数据中的特征模式,并将其映射到输出空间。例如,在图像分类任务中,前向传播可以将输入的图像转换为不同类别的概率分布,从而判断图像所属的类别。它为后续的反向传播提供了预测结果,是整个深度学习训练过程的基础步骤。

反向传播(Backward Propagation)
  1. 定义

反向传播是深度学习中用于计算损失函数(Loss Function)关于模型参数(权重和偏置)的梯度的算法。它基于链式法则,从输出层开始,将损失函数的误差沿着计算图反向传播到输入层,依次计算每一层参数的梯度。简单来说,反向传播是在已知前向传播得到的预测结果和真实标签的情况下,计算如何调整模型参数可以减小损失的过程。

  1. 作用

为模型参数的更新提供依据。在深度学习中,通常使用优化算法(如随机梯度下降,Stochastic Gradient Descent,SGD)来更新模型的参数,而这些优化算法需要知道损失函数关于参数的梯度。反向传播通过高效地计算这些梯度,使得模型能够根据预测误差自动调整参数,从而不断优化模型的性能。通过多次迭代前向传播和反向传播,模型可以逐渐学习到输入数据和输出标签之间的映射关系,提高预测的准确性。

计算图与梯度追踪(可略过)
  1. requires_grad设置张量是否需要进行梯度追踪

在深度学习模型训练过程中,需要计算损失函数关于模型参数的梯度,以便使用优化算法(如随机梯度下降)更新参数。通过将模型参数张量的 requires_grad 设置为 True,可以利用自动求导机制自动计算梯度。

1
2
3
4
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
y.backward()
print(x.grad) # 输出 4.0

requires_grad 是 PyTorch 张量的一个属性,当将其设置为 True 时,PyTorch 会开始追踪该张量的所有操作,构建计算图。计算图是一个有向无环图,它记录了从输入张量到输出张量的所有操作路径。

在上述示例中,我们创建了一个张量 x 并将 requires_grad 设置为 True,然后定义了一个函数 y = x ** 2。PyTorch 会自动记录这个操作,构建相应的计算图。

调用 y.backward() 方法时,PyTorch 会根据链式法则沿着计算图反向传播,计算出 y 关于 x 的梯度,并将梯度存储在 x.grad 属性中。

1
x 的梯度: tensor([4.])
  1. grad_fn 属性与反向传播链

grad_fn:PyTorch 张量的属性,指向创建该张量的函数对象,用于记录操作历史以支持反向传播求梯度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch

# 创建一个需要梯度追踪的张量
x = torch.tensor([2.0], requires_grad=True)
# 定义操作 y = x^2
y = x ** 2
# 定义操作 z = y * 3
z = y * 3

# 打印每个张量的 grad_fn
print("y 的 grad_fn:", y.grad_fn)
print("z 的 grad_fn:", z.grad_fn)

# 进行反向传播
z.backward()
# 打印 x 的梯度
print("x 的梯度:", x.grad)

在深度学习模型训练时,需要计算损失函数关于模型参数的梯度。由于模型中存在大量复杂的运算,通过 grad_fn 记录的操作历史,PyTorch 可以构建反向传播链,从而准确计算出梯度,为参数更新提供依据。

在上述代码中,首先创建了一个开启梯度追踪的张量 x。当执行 $y = x^2$操作时,PyTorch 会创建一个表示平方操作的函数对象,y.grad_fn 就会指向这个函数对象,以此记录下 y 是由 x 经过平方操作得到的。接着执行 $z = 3y$,同样地,z.grad_fn 会指向表示乘法操作的函数对象,记录 z 的计算来源。

当调用 z.backward() 时,PyTorch 会从 z 开始,依据 z.grad_fn 找到创建 z 的操作,然后通过这个操作回溯到 y,再根据 y.grad_fn 回溯到 x。在这个回溯过程中,按照链式法则逐步计算出 z 关于 x 的梯度,并将其存储在 x.grad 中。

1
2
3
y 的 grad_fn: <PowBackward0 object at 0x...>
z 的 grad_fn: <MulBackward0 object at 0x...>
x 的梯度: tensor([12.])

<PowBackward0 object at 0x...> 是 PyTorch 中用于表示反向传播过程中特定操作的梯度计算函数对象的字符串表示形式。

反向传播函数对象:在 PyTorch 的自动求导机制里,对张量进行各种运算(如加法、乘法、幂运算等)时,PyTorch 会构建一个计算图来记录这些操作的顺序和依赖关系。每个操作在计算图中都对应一个前向传播函数(用于计算输出结果)和一个反向传播函数(用于计算梯度)。

PowBackward0 的意义:PowBackward0 表示的是幂运算的反向传播函数。执行 $y = x^2$这样的幂运算时,ygrad_fn 属性就会指向一个 PowBackward0 对象,这个对象负责在反向传播过程中计算关于输入张量 x 的梯度。at 0x...at 0x... 后面跟着的是该对象在内存中的地址。这个地址是系统为该对象分配的唯一标识符,用于在内存中定位该对象。

梯度控制(可略过)

1.torch.no_grad() 上下文管理器

用于临时禁止 PyTorch 的梯度计算功能,在其作用域内创建或操作的张量不会进行梯度追踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch

# 创建一个需要梯度追踪的张量
x = torch.tensor([2.0], requires_grad=True)

# 使用 torch.no_grad() 上下文管理器
with torch.no_grad():
y = x ** 2
print("y 是否进行梯度追踪:", y.requires_grad)

# 在上下文管理器外进行操作
z = x ** 2
print("z 是否进行梯度追踪:", z.requires_grad)
1
2
y 是否进行梯度追踪: False
z 是否进行梯度追踪: True(z在上下文管理器外进行操作)

2.detach() 分离计算图

detach():用于将张量从当前计算图中分离,返回一个和原张量数据相同但不记录梯度信息、不参与反向传播的新张量。

  • 在模型评估阶段,只需得到预测结果,无需计算梯度,用 detach() 可节省内存和计算资源。
  • 在多模型联合训练时,若某个模型输出用于另一模型但不影响自身梯度计算,可使用 detach() 分离。(核心还是节省内存和计算资源)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch

# 创建需梯度追踪的张量
x = torch.tensor([3.0], requires_grad=True)
y = 2 * x
# 分离计算图
y_detached = y.detach()

print("原张量 y 是否追踪梯度:", y.requires_grad)
print("分离后的张量 y_detached 是否追踪梯度:", y_detached.requires_grad)

try:
y_detached.backward()
except RuntimeError as e:
print(f"错误信息: {e}")

上述代码里,x 开启梯度追踪,y = 2 * x 会记录在计算图中。调用 y.detach() 后得到 y_detached,它和 y 数据相同,但不追踪梯度。尝试对 y_detached 反向传播会报错,因为它已和计算图分离,无梯度信息。

1
2
3
原张量 y 是否追踪梯度: True
分离后的张量 y_detached 是否追踪梯度: False
错误信息: element 0 of tensors does not require grad and does not have a grad_fn

3.zero_grad() (梯度清零)

在深度学习模型的训练过程中,通常会按批次(batch)输入数据进行训练。每次反向传播计算得到的梯度会累加到模型参数的 .grad 属性中。如果不进行梯度清零,那么下一次计算的梯度会与之前的梯度累加,导致梯度计算错误。因此,在每个批次训练开始前,需要调用 zero_grad() 方法将梯度清零,以确保每个批次的梯度计算是独立的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的线性模型
model = nn.Linear(10, 1)
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 模拟输入数据
input_tensor = torch.randn(1, 10)
target = torch.randn(1, 1)

# 前向传播
output = model(input_tensor)
# 计算损失
criterion = nn.MSELoss()
loss = criterion(output, target)

# 反向传播计算梯度
loss.backward()
print("反向传播后第一个参数的梯度:", model.weight.grad)

# 梯度清零
optimizer.zero_grad()
print("梯度清零后第一个参数的梯度:", model.weight.grad)
1
2
反向传播后第一个参数的梯度: tensor([[ 0.1234, -0.5678, ...]])
梯度清零后第一个参数的梯度: tensor([[ 0., 0., ...]])

神经网络层

核心基类 nn.Module
什么是核心基类 nn.Module

nn.Module:PyTorch 中所有神经网络模块的基类,用于构建自定义的神经网络模型,封装了模型的结构和参数,方便进行前向传播、参数管理和模型保存等操作。

在深度学习中,我们需要构建各种各样的神经网络模型,如卷积神经网络(CNN)、循环神经网络(RNN)等。nn.Module 提供了一个统一的框架,使得我们可以方便地定义和管理这些模型。无论是简单的全连接网络还是复杂的深度网络,都可以通过继承 nn.Module 来构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
import torch.nn as nn

# 自定义一个简单的神经网络模型,继承自 nn.Module
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
# 定义一个全连接层,输入维度为 10,输出维度为 1
self.fc = nn.Linear(10, 1)

def forward(self, x):
# 定义前向传播过程
x = self.fc(x)
return x

# 创建模型实例
model = SimpleNet()
# 模拟输入数据
input_tensor = torch.randn(1, 10)
# 进行前向传播
output = model(input_tensor)

print("模型结构:", model)
print("输入张量形状:", input_tensor.shape)
print("输出张量形状:", output.shape)
  • __init__ 方法:在自定义的模型类中,__init__ 方法用于初始化模型的各个层。通过调用 super(SimpleNet, self).__init__() 确保父类 nn.Module 的初始化被正确执行。然后定义了一个全连接层 self.fc
  • forward 方法forward 方法定义了模型的前向传播过程,即输入数据如何经过各个层得到输出。在这个例子中,输入数据 x 经过全连接层 self.fc 得到输出。
  • 模型实例化和前向传播:创建 SimpleNet 的实例 model 后,将输入张量 input_tensor 传递给 model 就相当于调用了 forward 方法进行前向传播,得到输出 output
1
2
3
4
5
模型结构: SimpleNet(
(fc): Linear(in_features=10, out_features=1, bias=True)
)
输入张量形状: torch.Size([1, 10])
输出张量形状: torch.Size([1, 1])
自定义模型继承方法
  1. 参数管理parameters()named_parameters()

parameters():是 nn.Module 类的一个方法,它返回一个包含模型所有可学习参数的迭代器。通过遍历这个迭代器,能依次获取到模型中的各个参数张量,但不会提供参数的名称信息。

named_parameters():同样是 nn.Module 类的方法,它返回一个迭代器,该迭代器会生成模型可学习参数的名称和对应参数张量的元组。借助这个方法,我们不仅能获取参数张量,还能明确每个参数对应的名称,这在模型参数管理和调试时非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import torch
import torch.nn as nn

# 定义一个简单的神经网络模型,继承自 nn.Module
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
# 定义一个全连接层,输入维度为 10,输出维度为 5
self.fc1 = nn.Linear(10, 5)
# 定义另一个全连接层,输入维度为 5,输出维度为 1
self.fc2 = nn.Linear(5, 1)

def forward(self, x):
x = self.fc1(x)
x = self.fc2(x)
return x

# 创建模型实例
model = SimpleNet()

# 使用 parameters() 方法获取模型参数的迭代器
print("使用 parameters() 获取参数:")
params = model.parameters()
for param in params:
print(f"参数形状: {param.shape}")

# 使用 named_parameters() 方法获取模型带名称的参数迭代器
print("\n使用 named_parameters() 获取参数:")
named_params = model.named_parameters()
for name, param in named_params:
print(f"参数名称: {name}, 参数形状: {param.shape}")

parameters():主要用于优化器初始化。在训练模型时,优化器(如 torch.optim.SGDtorch.optim.Adam 等)需要知道模型的可学习参数,以便对这些参数进行梯度更新。由于优化器只关心参数张量本身,不需要参数名称,所以使用 parameters() 即可。

named_parameters():在模型调试、参数分组优化或模型参数的选择性加载时非常有用。例如,你可能只想更新模型中某些特定层的参数,通过参数名称可以方便地筛选出这些参数;或者在加载预训练模型时,根据参数名称选择性地加载部分参数。

1
2
3
4
5
6
7
8
9
10
11
使用 parameters() 获取参数:
参数形状: torch.Size([5, 10])
参数形状: torch.Size([5])
参数形状: torch.Size([1, 5])
参数形状: torch.Size([1])

使用 named_parameters() 获取参数:
参数名称: fc1.weight, 参数形状: torch.Size([5, 10])
参数名称: fc1.bias, 参数形状: torch.Size([5])
参数名称: fc2.weight, 参数形状: torch.Size([1, 5])
参数名称: fc2.bias, 参数形状: torch.Size([1])
  1. train()eval() 模式切换

train()eval()nn.Module 类中的方法,用于切换模型的训练和评估模式。train() 方法将模型设置为训练模式,eval() 方法将模型设置为评估模式,不同模式下部分层(如 DropoutBatchNorm)会有不同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch
import torch.nn as nn

# 定义一个包含 Dropout 层的简单神经网络模型
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(10, 5)
self.dropout = nn.Dropout(p=0.5)
self.fc2 = nn.Linear(5, 1)

def forward(self, x):
x = self.fc1(x)
x = self.dropout(x)
x = self.fc2(x)
return x

# 创建模型实例
model = SimpleNet()

# 设置为训练模式
model.train()
print("训练模式下 Dropout 是否启用:", model.dropout.training)

# 设置为评估模式
model.eval()
print("评估模式下 Dropout 是否启用:", model.dropout.training)

# 模拟输入数据
input_tensor = torch.randn(1, 10)
with torch.no_grad():
output_eval = model(input_tensor)
model.train()
output_train = model(input_tensor)
print("评估模式输出:", output_eval)
print("训练模式输出:", output_train)
  • 训练模式(train():在模型训练阶段使用,此时模型中的 Dropout 层会按照设定的概率随机丢弃部分神经元,BatchNorm 层会根据当前批次的数据更新统计信息(如均值和方差),有助于提高模型的泛化能力。
  • 评估模式(eval():在模型评估、测试或者推理阶段使用。在评估模式下,Dropout 层不再丢弃神经元,BatchNorm 层使用训练阶段统计得到的均值和方差进行归一化操作,保证评估结果的稳定性和一致性。
1
2
3
4
训练模式下 Dropout 是否启用: True
评估模式下 Dropout 是否启用: False
评估模式输出: tensor([[...]], grad_fn=<AddmmBackward0>)
训练模式输出: tensor([[...]], grad_fn=<AddmmBackward0>)

这里具体的输出数值会因随机生成的输入数据和模型初始化不同而有所变化,但可以看到在不同模式下 Dropout 层的行为不同,导致输出结果也可能不同。

网络层 (Layers)
基础层
  1. nn.Linear全连接层

nn.Linear:PyTorch 中用于创建全连接层(也称为线性层)的类,它对输入数据进行线性变换,即执行矩阵乘法和加法操作,可用于构建各种神经网络模型。

神经网络基础构建:全连接层是神经网络中最基本的组成部分之一,常用于多层感知机(MLP)的构建,可处理各种类型的数据,如图像、文本等特征向量。

特征映射:可以将输入数据从一个特征空间映射到另一个特征空间,有助于模型学习数据中的复杂模式和特征表示。


为什么是全连接层?

1.连接方式:在全连接层中,每一个输入神经元都与每一个输出神经元相连接,这种连接是 “全” 的,即完全连接。对于

nn.Linear(in_features, out_features) ,输入的 in_features 个神经元和输出的 out_features 个神经元之间存在着完整的连接关系。

2.数学运算:假设输入向量$X$维度为$n$(即 in_features),输出向量$Y$维度为$m$(即 out_features),全连接层通过权重矩阵$W$(形状为$m × n$)和偏置向量$b$(形状为$m$)进行线性变换$Y=WX+b$。这里的权重矩阵$W$描述了输入神经元和输出神经元之间所有可能的连接强度,每一个输入元素都会影响到每一个输出元素的计算结果,这种全面的连接关系是 “全连接” 概念的核心体现。

3.网络结构对比:在神经网络中,除了全连接层,还有其他类型的层,比如卷积层、池化层等。卷积层中,神经元只与输入数据的局部区域相连接,而不是像全连接层那样与所有输入神经元连接;池化层则主要进行下采样操作,不存在像全连接层这样全面的神经元连接模式。因此,为了突出这种所有输入和输出神经元之间都有连接的特殊结构,将其称为全连接层,以便和其他类型的层进行区分。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.nn as nn

# 创建一个全连接层,输入维度为 10,输出维度为 5
linear_layer = nn.Linear(10, 5)

# 模拟输入数据,假设有 1 个样本,每个样本有 10 个特征
input_tensor = torch.randn(1, 10)

# 进行前向传播
output = linear_layer(input_tensor)

print("全连接层的权重形状:", linear_layer.weight.shape)
print("全连接层的偏置形状:", linear_layer.bias.shape)
print("输入张量的形状:", input_tensor.shape)
print("输出张量的形状:", output.shape)
  • 初始化参数nn.Linear(in_features, out_features) 中,in_features 表示输入特征的数量,out_features 表示输出特征的数量。在上述代码中,in_features = 10out_features = 5,意味着输入的每个样本有 10 个特征,经过全连接层后输出的每个样本有 5 个特征。
  • 线性变换:全连接层的计算过程可以表示为$Y=XW^T+b$ ,其中$X$是输入向量,$W$是权重矩阵,形状为 (out_features, in_features),$b$是偏置向量,形状为 (out_features,),$Y$是输出向量。
  • 前向传播:将输入张量 input_tensor 传递给 linear_layer 时,会自动执行上述线性变换,得到输出张量 output
1
2
3
4
全连接层的权重形状: torch.Size([5, 10])
全连接层的偏置形状: torch.Size([5])
输入张量的形状: torch.Size([1, 10])
输出张量的形状: torch.Size([1, 5])
  • [ ] 权重形状的确定:

为了使矩阵乘法$WX$能够得到维度为$m$的输出向量,权重矩阵$W$的形状必须是$m × n$。这是因为在矩阵乘法中,两个矩阵能够相乘的条件是前一个矩阵的列数等于后一个矩阵的行数,并且相乘结果矩阵的行数等于前一个矩阵的行数,列数等于后一个矩阵的列数。即如果$W$是$m × n$矩阵,$X$是$n × 1$向量,那么$WX$的结果就是一个$m × 1$向量,符合输出向量$Y$的维度要求。

在代码示例中,输入特征数量 in_features = 10,输出特征数量 out_features = 5,所以权重矩阵 的形状就是 (5, 10),即 torch.Size([5, 10])

  • [ ] 偏置形状的确定

偏置向量$b$的作用是在经过矩阵乘法得到的结果上进行偏移。由于输出向量$Y$的维度是$m$,为了能够对$WX$的每一个元素都加上一个偏移量,偏置向量$b$的维度也必须是$m$,即$b∈\R^m$。

在代码示例中,输出特征数量 out_features = 5,所以偏置向量 的形状就是 (5,),即 torch.Size([5])

  1. nn.Bilinear双线性层

nn.Bilinear:PyTorch 中的一个类,用于创建双线性层。双线性层对两个输入进行双线性变换,能捕捉两个输入之间的交互信息,是一种比普通线性层更复杂的变换形式。

关系建模:在需要捕捉两个不同特征之间交互关系的任务中非常有用。例如,在推荐系统中,可以用双线性层来建模用户特征和物品特征之间的交互,以预测用户对物品的偏好;在自然语言处理中,可用于处理两个不同句子或不同语义表示之间的关系。

融合多模态信息:当处理多模态数据(如图像和文本)时,双线性层可以帮助融合不同模态之间的信息,挖掘它们之间的潜在关联。

image-20250222212028325

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch
import torch.nn as nn

# 创建一个双线性层
# 第一个输入维度为 10,第二个输入维度为 20,输出维度为 5
bilinear_layer = nn.Bilinear(10, 20, 5)

# 模拟两个输入张量
# 第一个输入:假设有 1 个样本,每个样本有 10 个特征
input1 = torch.randn(1, 10)
# 第二个输入:假设有 1 个样本,每个样本有 20 个特征
input2 = torch.randn(1, 20)

# 进行前向传播
output = bilinear_layer(input1, input2)

print("双线性层的权重形状:", bilinear_layer.weight.shape)
print("双线性层的偏置形状:", bilinear_layer.bias.shape)
print("第一个输入张量的形状:", input1.shape)
print("第二个输入张量的形状:", input2.shape)
print("输出张量的形状:", output.shape)

权重张量的形状符合 (out_features, in1_features, in2_features),偏置向量的长度等于输出特征的数量,两个输入张量经过双线性层后,输出张量的特征数量变为 out_features 设定的值。

1
2
3
4
5
双线性层的权重形状: torch.Size([5, 10, 20])
双线性层的偏置形状: torch.Size([5])
第一个输入张量的形状: torch.Size([1, 10])
第二个输入张量的形状: torch.Size([1, 20])
输出张量的形状: torch.Size([1, 5])
卷积层

1.什么是卷积层?

卷积层本质上是通过可学习的卷积核(滤波器)在输入数据上进行滑动并执行卷积操作,以提取输入数据中的局部特征模式,同时利用参数共享减少模型参数数量。

卷积操作指的是卷积核(一个小的矩阵)在输入数据(如图像矩阵)上按一定步长滑动,每滑动到一个位置,就将卷积核与该位置对应的输入局部区域元素对应相乘后求和,得到输出特征图的一个值,不断滑动直至覆盖整个输入数据,最终生成完整的输出特征图。

假设输入是一个 4×4 的单通道图像矩阵,使用一个 2×2 的卷积核进行卷积操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
输入图像矩阵:
[[1 2 3 4]
[5 6 7 8]
[9 10 11 12]
[13 14 15 16]]

卷积核:
[[1 2]
[3 4]]

第一步:卷积核位于输入图像的左上角,覆盖的局部区域是:
[[1 2]
[5 6]]
将卷积核与该局部区域对应元素相乘再求和:
(1x1+2x2)+(3x5+4x6)=44
这个 44 就是输出特征图左上角的值。

第二步:卷积核向右滑动一个步长(假设步长为 1),覆盖的局部区域变为:
[[2 3]
[6 7]]
同样进行对应元素相乘再求和的操作:
(1x2+2x3)+(3x6+4x7)=54
这是输出特征图中左上角右侧位置的值。

后续步骤:卷积核继续向右、向下滑动,每次都重复上述相乘求和的操作,直到遍历完整个输入图像矩阵,最终得到一个 3×3 的输出特征图(因为 4×4 的输入矩阵使用 2×2 卷积核,步长为 1 时会得到 3×3 的输出)。

2.卷积层有什么好处?

减少参数数量

卷积层使用参数共享机制,即一个卷积核在整个输入数据上滑动使用,相比于全连接层每个输出神经元都与所有输入相连,大大减少了需要学习的参数数量,降低计算量和存储需求,也减少了过拟合风险。

提取局部特征

卷积核在输入数据的局部区域进行操作,能够有效捕捉数据中的局部特征,如在图像中可检测边缘、纹理等。这些局部特征在不同位置可能具有相似性,卷积层可以很好地学习和利用这种特性。

保留空间结构

卷积操作基于局部连接,能保留输入数据的空间结构信息,这对于处理具有空间结构的数据(如图像、音频)非常重要,有助于模型理解数据中元素之间的相对位置关系。

可构建深层网络

多个卷积层可以堆叠形成深层卷积神经网络,随着网络深度增加,能学习到从简单到复杂、从底层到高层的多层次特征,提升模型在各种任务(如图像分类、目标检测)中的性能。


  1. nn.Conv1d 1D卷积:处理时序数据(如音频、文本)

nn.Conv1d 是 PyTorch 中用于进行一维卷积操作的模块,主要用于处理时序数据,像音频信号、文本序列等。一维卷积在这些数据上沿着一个维度(通常是时间维度)进行卷积操作,能有效提取数据中的局部特征模式。

音频处理:可以用于音频特征提取、语音识别等任务,通过一维卷积提取音频信号中的时域特征。

文本处理:在自然语言处理中,将文本序列看作一维数据,一维卷积可以捕捉文本中的局部语义信息,常用于文本分类、情感分析等任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch
import torch.nn as nn

# 定义输入数据
# 输入数据形状:(批量大小, 输入通道数, 序列长度)
input_tensor = torch.randn(16, 3, 100)

# 定义一维卷积层
# in_channels: 输入通道数
# out_channels: 输出通道数
# kernel_size: 卷积核大小
conv1d_layer = nn.Conv1d(in_channels=3, out_channels=6, kernel_size=3)

# 进行卷积操作
output = conv1d_layer(input_tensor)

print("输入张量形状:", input_tensor.shape)
print("输出张量形状:", output.shape)

输入数据

通常是三维张量,形状为 (batch_size, in_channels, sequence_length)。其中 batch_size 表示一次处理的样本数量;in_channels 是输入数据的通道数,例如在音频处理中,单声道音频 in_channels 为 1,立体声音频 in_channels 为 2;sequence_length 是序列的长度,对于音频数据就是音频信号的采样点数,对于文本数据就是文本序列的长度。

卷积层参数

in_channels:输入数据的通道数,必须与输入张量的第二维大小一致。

out_channels:输出数据的通道数,即卷积层使用的卷积核数量。每个卷积核会提取一种特定的特征,因此不同的卷积核会输出不同的特征图。

kernel_size:卷积核的大小,表示在序列维度上卷积核覆盖的元素个数。例如 kernel_size=3 表示卷积核在序列上每次覆盖 3 个元素。

卷积操作过程

卷积核在输入数据的序列维度上滑动,每次覆盖 kernel_size 个元素,并在每个通道上进行卷积操作,然后将各通道的结果相加(如果有多个输入通道),最后加上偏置项得到输出的一个值。卷积核不断滑动,最终得到输出特征图。

输出数据

输出数据同样是三维张量,形状为 (batch_size, out_channels, new_sequence_length)。其中 batch_size 与输入相同;out_channels 是卷积层定义的输出通道数;new_sequence_length 由输入序列长度、卷积核大小、步长(stride,默认为 1)和填充(padding,默认为 0)等因素决定,计算公式为:

image-20250222215408498

1
2
输入张量形状: torch.Size([16, 3, 100])
输出张量形状: torch.Size([16, 6, 98])
  1. nn.Conv2d 2D卷积:处理图像数据

nn.Conv2d:PyTorch 中用于实现二维卷积操作的类,主要处理图像数据。通过可学习的卷积核在输入图像上滑动并进行卷积运算,提取图像局部特征,利用参数共享减少模型参数。

图像分类:用于提取图像特征,结合后续层将图像映射到不同类别,如区分猫狗图像。

目标检测:提取物体特征,配合检测算法确定图像中物体的位置和类别,像自动驾驶中检测车辆、行人。

语义分割:对图像每个像素分类,将图像分割成不同语义区域,例如医学图像中分割肿瘤和正常组织。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import torch
import torch.nn as nn

# 准备输入数据,仅保留第一张图片
batch_size = 1
in_channels = 3
height = 4
width = 4
# 手动设定输入张量,方便后续手动计算验证
input_tensor = torch.tensor([
[
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]],
[[2, 3, 4, 5],
[6, 7, 8, 9],
[10, 11, 12, 13],
[14, 15, 16, 17]],
[[3, 4, 5, 6],
[7, 8, 9, 10],
[11, 12, 13, 14],
[15, 16, 17, 18]]
]
], dtype=torch.float32)

# 定义卷积层
out_channels = 2
kernel_size = 2
stride = 1
padding = 0
conv2d_layer = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
kernel_size=kernel_size, stride=stride, padding=padding)

# 手动设定卷积核权重,方便后续手动计算验证
# 这里假设的卷积核和前面理论部分一致
conv2d_layer.weight.data = torch.tensor([
[
[[1, 0],
[0, 1]],
[[0, 1],
[1, 0]],
[[1, 1],
[1, 1]]
],
[
[[1, 1],
[1, 1]],
[[0, 0],
[0, 0]],
[[1, 0],
[0, 1]]
]
], dtype=torch.float32)

# 手动设定偏置为 0,简化计算
conv2d_layer.bias.data = torch.zeros(out_channels)

# 进行卷积操作
output = conv2d_layer(input_tensor)

print("输入张量形状:", input_tensor.shape)
print("卷积核形状:", conv2d_layer.weight.shape)
print("输出张量形状:", output.shape)
print("第一张图片的输出:", output[0])

# 手动计算第一张图片经过第一个卷积核的左上角输出值
first_kernel_channel1 = torch.tensor([[1, 0], [0, 1]], dtype=torch.float32)
first_kernel_channel2 = torch.tensor([[0, 1], [1, 0]], dtype=torch.float32)
first_kernel_channel3 = torch.tensor([[1, 1], [1, 1]], dtype=torch.float32)

# 第一个通道的局部卷积
local_conv_channel1 = (input_tensor[0, 0, :2, :2] * first_kernel_channel1).sum()
# 第二个通道的局部卷积
local_conv_channel2 = (input_tensor[0, 1, :2, :2] * first_kernel_channel2).sum()
# 第三个通道的局部卷积
local_conv_channel3 = (input_tensor[0, 2, :2, :2] * first_kernel_channel3).sum()

# 多通道结果相加
manual_output = local_conv_channel1 + local_conv_channel2 + local_conv_channel3
print("手动计算第一张图片经过第一个卷积核左上角输出值:", manual_output)

上述代码模拟了处理彩色图像的过程。输入数据是一个四维张量,形状为 (batch_size, in_channels, height, width)。例如,batch_size = 1 表示一次处理 1 张图像;in_channels = 3 对应 RGB 三个通道;height = 4width = 4 是图像的高度和宽度。输入数据可以表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输入数据形状: (2, 3, 4, 4)
第 1 张图像通道 1:
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]]

第 1 张图像通道 2:
[[2, 3, 4, 5],
[6, 7, 8, 9],
[10, 11, 12, 13],
[14, 15, 16, 17]]

第 1 张图像通道 3:
[[3, 4, 5, 6],
[7, 8, 9, 10],
[11, 12, 13, 14],
[15, 16, 17, 18]]

...(其他图片类似)

定义一个二维卷积层,设置参数如下:

  • in_channels = 3,与输入图像的通道数一致。
  • out_channels = 2,表示使用 2 个卷积核。
  • kernel_size = 2,卷积核是 2x2 的正方形。
  • stride = 1,卷积核每次滑动 1 个单位。
  • padding = 0,不进行填充。

每个卷积核也是一个四维张量,形状为 (out_channels, in_channels, kernel_height, kernel_width),这里是 (2, 3, 2, 2)。假设两个卷积核分别为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
卷积核 1:
通道 1:
[[1, 0],
[0, 1]]

通道 2:
[[0, 1],
[1, 0]]

通道 3:
[[1, 1],
[1, 1]]

卷积核 2:
通道 1:
[[1, 1],
[1, 1]]

通道 2:
[[0, 0],
[0, 0]]

通道 3:
[[1, 0],
[0, 1]]
1
2
3
4
5
6
7
卷积操作
以第 1 张图像为例,对于卷积核 1:
通道 1 的卷积:卷积核在通道 1 的输入图像上滑动,计算局部区域与卷积核对应元素相乘再求和。例如,在左上角区域:
通道 2 和通道 3 同样操作:得到各自的局部卷积结果。
多通道结果相加:将三个通道的局部卷积结果相加,得到该位置的最终输出值。
滑动卷积核:卷积核在图像上按步长 1 滑动,重复上述操作,得到卷积核 1 对第 1 张图像的输出特征图。
对于卷积核 2 也进行同样的操作,最终得到两个输出特征图,这两个特征图组成了第 1 张图像经过卷积层后的输出。对于第 2 张图像,也重复上述卷积操作。
1
2
3
4
5
6
7
8
9
10
11
12
输入张量形状: torch.Size([1, 3, 4, 4])
卷积核形状: torch.Size([2, 3, 2, 2])
输出张量形状: torch.Size([1, 2, 3, 3])
第一张图片的输出:
tensor([[[ 41., 48., 55.],
[ 65., 72., 79.],
[ 89., 96., 103.]],

[[ 26., 30., 34.],
[ 46., 50., 54.],
[ 66., 70., 74.]]], grad_fn=<SelectBackward0>)
手动计算第一张图片经过第一个卷积核左上角输出值: tensor(41.)

卷积核如何确定?

卷积核的确定需要综合考虑任务需求、数据特点、模型架构和计算资源等因素,并通过实验和调优不断优化,以达到最佳的性能。


  1. nn.Conv3d 3D卷积:处理视频/体数据

nn.Conv3d 是 PyTorch 中用于实现三维卷积操作的类,主要用于处理视频数据(包含时间维度和空间维度)或体数据(如医学影像中的三维体数据)。它通过可学习的三维卷积核在输入数据上进行滑动并执行卷积运算,提取数据中的三维特征,利用参数共享机制减少模型参数数量,降低计算复杂度。

视频分类:在视频分类任务中,nn.Conv3d 用于提取视频的时空特征,通过多个卷积层和池化层的组合,将视频特征映射到不同的类别。例如,在识别视频中的动作类型(如跑步、跳舞、打球等)时,三维卷积可以捕捉视频中随时间变化的动作特征。

医学影像分析:在医学影像分析中,如对 CT 或 MRI 扫描得到的三维体数据进行分析,nn.Conv3d 可以用于肿瘤检测、器官分割等任务。它能够提取体数据中的三维结构信息,帮助医生进行疾病诊断。

自动驾驶场景:在自动驾驶中处理点云数据(可以看作三维空间中的数据)时,nn.Conv3d 可用于识别道路上的障碍物、车辆、行人等目标,通过分析点云数据的三维特征来辅助决策。

卷积核与卷积操作

三维卷积核是一个三维的矩阵,其元素是可学习的权重参数。在进行卷积操作时,三维卷积核会在输入的三维数据(如视频的多个帧组成的序列或者三维体数据)上按一定的步长进行滑动,每次覆盖一个与卷积核大小相同的三维局部区域。对于这个局部区域,将卷积核与该区域的元素对应相乘后求和,得到一个输出值,这个值就构成了输出特征体的一个元素。随着卷积核不断滑动,遍历整个输入三维数据,最终生成完整的输出特征体。


nn.Conv1d/2d/3d的共同原理与特征

共同原理

1.卷积操作核心

核心都是卷积运算。卷积核(也称为滤波器)是一组可学习的权重参数,在输入数据上按照一定规则滑动,每次覆盖一个局部区域,将卷积核与该局部区域的元素对应相乘后求和,得到一个输出值。这个过程不断重复,直到卷积核遍历完整个输入数据,从而生成输出特征。

nn.Conv1d 为例,输入是一维序列,卷积核也是一维的,在序列上滑动进行卷积;nn.Conv2d 输入是二维图像,卷积核是二维矩阵,在图像上滑动;nn.Conv3d 输入是三维数据(如视频或体数据),卷积核是三维的,在三维空间中滑动。

2.线性变换

卷积操作本质上是一种线性变换。对于输入数据的每个局部区域,卷积运算将其与卷积核进行加权求和,这相当于对输入数据进行了线性组合。这种线性变换使得模型能够学习到输入数据中的特征模式。

3.参数共享

在卷积过程中,卷积核的权重在整个输入数据上是共享的。也就是说,同一个卷积核在不同的位置进行卷积操作时,使用的是相同的权重参数。这大大减少了模型需要学习的参数数量,降低了计算复杂度,同时也提高了模型的泛化能力,使得模型能够在不同位置检测到相同的特征。

4.多通道处理

当输入数据具有多个通道时,每个卷积核也具有对应数量的通道。卷积操作会在每个通道上分别进行卷积,然后将各通道的结果相加,得到最终的输出值。例如,对于 RGB 图像(3 个通道),每个二维卷积核也有 3 个通道,分别对 R、G、B 通道进行卷积后求和。nn.Conv1dnn.Conv2dnn.Conv3d 都支持多通道输入和输出,通过使用多个卷积核可以得到多个通道的输出特征。

共同特征

1.可学习性

卷积核的权重参数是可学习的,在模型训练过程中,通过反向传播算法和优化器(如随机梯度下降、Adam 等)不断调整卷积核的权重,使得模型能够自动学习到输入数据中的有效特征,以适应不同的任务需求,如分类、检测、分割等。

2.局部感知

都具有局部感知的特性,即卷积核只关注输入数据的局部区域,而不是全局信息。这种局部感知能力使得模型能够捕捉到数据中的局部特征,如边缘、纹理、模式等。通过堆叠多个卷积层,模型可以逐渐学习到更高级、更抽象的特征。

3.平移不变性

由于参数共享的特性,卷积操作具有平移不变性。这意味着如果输入数据中的某个特征在不同位置出现,卷积核都能够检测到该特征,而不依赖于其具体位置。这种平移不变性使得模型在处理具有平移特性的数据时更加有效,例如图像中的物体在不同位置出现,模型都能够正确识别。

4.输出特征的维度调整

通过调整卷积核的大小、步长和填充等参数,可以控制输出特征的维度。步长越大,输出特征的维度越小;填充可以在输入数据的边缘添加额外的元素,从而保持输出特征的维度与输入数据相同或满足特定的要求。这种灵活性使得模型能够根据具体任务和数据特点进行合理的设计。


  1. nn.ConvTranspose1d/2d/3d 转置卷积:反卷积(上采样)

nn.ConvTranspose1dnn.ConvTranspose2dnn.ConvTranspose3d 分别是 PyTorch 中用于一维、二维和三维转置卷积(也常被称为反卷积,但实际上并非严格意义的卷积逆运算,主要用于上采样)的类。转置卷积通过在输入数据上进行特殊的卷积操作,使得输出的尺寸比输入尺寸更大,实现数据的上采样,在图像生成、语义分割等任务中经常使用。

转置卷积本质上是标准卷积的一种 “逆向” 操作,但不是严格意义上的逆运算。在标准卷积中,卷积核在输入数据上滑动,将局部区域映射到一个输出值,导致输出尺寸通常小于输入尺寸。而转置卷积则是将输入的每个元素扩展到一个更大的区域,然后与卷积核进行卷积操作,从而实现输出尺寸大于输入尺寸的效果。

具体来说,转置卷积通过在输入数据周围插入零值(称为 “零填充”),并调整卷积核的滑动方式,使得卷积操作能够产生更大的输出。在一维、二维和三维的情况下,分别使用 nn.ConvTranspose1dnn.ConvTranspose2dnn.ConvTranspose3d 来实现相应维度的转置卷积。

上采样

指的是将低分辨率的数据转换为高分辨率数据的过程。在深度学习中,输入数据(如图像、特征图等)经过一系列下采样操作(如卷积、池化)后尺寸会变小,为了恢复到原始尺寸或达到特定任务所需的尺寸,就需要进行上采样操作。

  • 恢复尺寸:在一些任务中,如语义分割,模型需要对输入图像的每个像素进行分类,经过下采样后的特征图尺寸变小,需要通过上采样恢复到与输入图像相同的尺寸,以便为每个像素分配类别标签。
  • 增加细节:上采样可以在一定程度上增加数据的细节信息,使模型能够学习到更丰富的特征,提升模型性能。

图像生成:在生成对抗网络(GAN)和变分自编码器(VAE)等图像生成模型中,转置卷积用于将低维的特征向量逐步上采样为高分辨率的图像。例如,在生成手写数字图像时,模型从一个随机的低维向量开始,通过一系列的转置卷积层逐渐生成 28x28 像素的手写数字图像。

语义分割:在语义分割任务中,模型需要将卷积层提取的低分辨率特征图恢复到与输入图像相同的尺寸,以便为每个像素分配一个类别标签。转置卷积可以有效地实现特征图的上采样,帮助模型生成与输入图像大小一致的分割结果。

超分辨率重建:超分辨率重建任务旨在将低分辨率的图像恢复为高分辨率的图像。转置卷积可以用于逐步增加图像的分辨率,提高图像的清晰度和细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn as nn

# 模拟低分辨率图像特征图,形状为 (批量大小, 输入通道数, 高度, 宽度)
input_image = torch.randn(1, 3, 8, 8)

# 定义二维转置卷积层,用于上采样图像
conv_transpose2d = nn.ConvTranspose2d(in_channels=3, out_channels=3, kernel_size=2, stride=2)

# 进行转置卷积操作
output_image = conv_transpose2d(input_image)

print("输入图像特征图形状:", input_image.shape)
print("输出图像特征图形状:", output_image.shape)
1
2
输入图像特征图形状: torch.Size([1, 3, 8, 8])
输出图像特征图形状: torch.Size([1, 3, 16, 16])
  1. nn.Conv1d/2d/3d (设置 dilation)空洞卷积:扩大感受野

nn.Conv1dnn.Conv2dnn.Conv3d 在设置 dilation 参数后可实现空洞卷积(也叫扩张卷积)。空洞卷积是对标准卷积的扩展,通过在卷积核元素之间插入空洞,在不增加参数数量的情况下扩大卷积核的感受野,使模型能够捕捉更大范围的上下文信息,常用于语义分割、目标检测等任务。

在标准卷积中,卷积核的元素是连续排列的,在输入数据上进行滑动卷积操作。而空洞卷积通过设置 dilation 参数,在卷积核元素之间插入空洞。例如,当 dilation = 2 时,卷积核元素之间会间隔一个位置,相当于在标准卷积核的基础上每隔一个元素设置为零,然后再进行卷积操作。这样,卷积核在输入数据上滑动时,能够覆盖更大的区域,从而扩大了感受野。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.nn as nn

# 定义输入张量
# 形状:(批量大小, 输入通道数, 高度, 宽度)
input_tensor = torch.randn(1, 3, 16, 16)

# 定义二维空洞卷积层
# 设置 dilation = 2 来实现空洞卷积
conv2d_dilated = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=1, dilation=2)

# 进行空洞卷积操作
output = conv2d_dilated(input_tensor)

print("输入张量形状:", input_tensor.shape)
print("输出张量形状:", output.shape)
1
2
输入张量形状: torch.Size([1, 3, 16, 16])
输出张量形状: torch.Size([1, 6, 16, 16])
循环神经网络层

循环神经网络层(Recurrent Neural Network layer,RNN layer)是一种专门用于处理序列数据的神经网络层结构。在很多实际应用场景中,数据具有序列特性,例如文本(由单词按顺序组成)、语音(音频信号随时间变化)、时间序列数据(如股票价格随时间的波动)等。与传统的前馈神经网络不同,循环神经网络层引入了循环结构,使得网络能够在处理序列数据时保留之前时间步的信息,从而更好地捕捉序列中的上下文关系和时间依赖。

RNN、LSTM 和 GRU 都属于循环神经网络层的范畴,它们在处理序列数据时各有特点。简单 RNN 结构简单但存在梯度问题;LSTM 通过门控机制解决了梯度问题但计算复杂;GRU 则在性能和计算效率之间取得了较好的平衡。根据不同的应用场景和数据特点,可以选择合适的循环神经网络层来构建模型。

1.nn.RNN RNN:基础循环网络

nn.RNN 是 PyTorch 中用于构建基础循环神经网络(Recurrent Neural Network,RNN)的模块。RNN 是一种专门处理序列数据的神经网络,它通过在网络中引入循环结构,使得网络能够保存和利用之前时间步的信息,从而对序列中的时间依赖关系进行建模。不过,RNN 存在梯度消失或梯度爆炸问题,在处理长序列时表现不佳。

在每个时间步 ,RNN 接收当前输入$x_t$和上一个时间步的隐藏状态$h_{t-1}$,通过以下公式计算当前时间步的隐藏状态$h_t$:

$h_t=tanh(W_{ih}x_t+W_{hh}h_{t-1}+b_h)$

其中$W_{ih}$是输入到隐藏状态的权重矩阵,$W_{hh}$是隐藏状态到隐藏状态的权重矩阵,$b_h$是偏置,$tanh$是激活函数,用于引入非线性。

自然语言处理:如文本分类任务,将一段文本看作一个词序列,RNN 可以对文本中的语义信息进行建模,根据之前的词来预测当前词的类别概率;还可用于语言生成,例如生成诗歌、故事等,通过不断根据之前生成的词来预测下一个词。

时间序列预测:像股票价格预测、天气预报等,把时间序列数据(如每天的股票价格、每小时的气温)作为输入,RNN 可以学习到序列中的趋势和模式,从而预测未来的值。

语音识别:语音信号是随时间变化的序列,RNN 可以处理语音特征序列,根据之前的语音帧信息来识别当前帧对应的语音内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
import torch.nn as nn

# 定义输入参数
input_size = 10 # 输入特征维度
hidden_size = 20 # 隐藏状态维度
num_layers = 1 # RNN 层数
batch_size = 32 # 批量大小
seq_len = 5 # 序列长度

# 创建 RNN 层
rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)

# 生成随机输入数据
input_data = torch.randn(batch_size, seq_len, input_size)

# 初始化隐藏状态
h_0 = torch.randn(num_layers, batch_size, hidden_size)

# 进行前向传播
output, h_n = rnn(input_data, h_0)

print("输入数据形状:", input_data.shape)
print("输出形状:", output.shape)
print("最终隐藏状态形状:", h_n.shape)

输入数据 input_data 是一个三维张量,当 batch_first = True 时,形状为 (batch_size, seq_len, input_size),表示批量大小为 32,序列长度为 5,每个时间步的输入特征维度为 10。

1
input_data = torch.randn(batch_size, seq_len, input_size)

使用 nn.RNN 创建 RNN 层,设置输入特征维度、隐藏状态维度和层数等参数。

1
rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)

初始化隐藏状态 h_0,形状为 (num_layers, batch_size, hidden_size)

1
h_0 = torch.randn(num_layers, batch_size, hidden_size)

调用 rnn 进行前向传播,得到输出 output 和最终隐藏状态 h_n。输出 output 包含每个时间步的隐藏状态,形状为 (batch_size, seq_len, hidden_size);最终隐藏状态 h_n 是最后一个时间步的隐藏状态,形状为 (num_layers, batch_size, hidden_size)

1
2
3
输入数据形状: torch.Size([32, 5, 10])
输出形状: torch.Size([32, 5, 20])
最终隐藏状态形状: torch.Size([1, 32, 20])

2.nn.LSTM LSTM:长短期记忆网络

nn.LSTM 是 PyTorch 中用于构建长短期记忆网络(Long Short - Term Memory,LSTM)的模块。LSTM 是一种特殊的循环神经网络(RNN),旨在解决传统 RNN 在处理长序列时遇到的梯度消失或梯度爆炸问题。它通过引入门控机制(输入门、遗忘门和输出门),能够有效地捕捉序列中的长距离依赖关系,在处理自然语言处理、时间序列分析等序列数据任务中表现出色。

梯度消失与梯度爆炸

1.梯度消失

在神经网络训练中,使用反向传播算法更新参数时,梯度会从输出层向输入层逐层传递。梯度消失指的是在这个过程中,梯度值变得越来越小,趋近于零。

传统 RNN 在计算梯度时涉及多个权重矩阵的连乘,若权重矩阵的元素值较小,经过多次连乘后梯度会急剧减小。激活函数(如 Sigmoid、Tanh)的导数取值范围在 0 到 1 之间,多次使用这类激活函数也会使梯度逐渐变小。

由于梯度极小,模型参数的更新幅度变得非常小,几乎不再更新,导致网络无法学习到长序列中的远距离依赖信息,难以收敛到最优解。

2.梯度爆炸

与梯度消失相反,梯度爆炸是指在反向传播过程中,梯度值变得越来越大,失去控制。

同样是因为多个权重矩阵的连乘,若权重矩阵的元素值较大,连乘后梯度会急剧增大。网络层数过深、学习率设置过大等也可能引发梯度爆炸。

过大的梯度会使模型参数更新幅度过大,导致参数值剧烈波动,模型无法稳定训练,甚至可能使训练过程发散。


LSTM 通过引入门控机制,能够有效地缓解梯度消失和梯度爆炸问题,使得网络在处理长序列数据时可以更好地保留和传递信息。


传统 RNN 易出现梯度消失或爆炸,因为反向传播时梯度经多时间步连乘,值要么趋于零、要么无限制增大。而门控机制可将梯度限制在一定区间。

以 LSTM 为例,遗忘门用sigmoid函数输出$[0,1]$的值,决定上一时刻细胞状态信息的保留程度。接近1时梯度顺畅传递,避免消失;接近0时切断部分传递路径,防止爆炸。

输入门同理控制当前输入信息的添加比例,和遗忘门协同让细胞状态渐进更新。这种平稳的信息传递使梯度也稳定,不会剧烈波动。

输出门对细胞状态输出信息缩放,控制从隐藏状态到细胞状态的梯度传递,避免细胞状态梯度过度影响隐藏状态更新,进一步稳定梯度。

LSTM 的核心在于其门控机制,主要包含以下几个部分:

遗忘门(Forget Gate):决定上一个时间步的细胞状态$C_{t-1}$中哪些信息需要被遗忘。计算公式为$f_t=\sigma(W_f[h_{t-1}.x_t]+b_f)$,其中$\sigma$是sigmoid函数, $W_f$是遗忘门的权重矩阵,$b_f$是偏置,$[h_{t-1},x_t]$表示将上一个时间步的隐藏状态$h_{t-1}$和当前输入$x_t$拼接起来。

输入门(Input Gate):决定当前输入$x_t$中哪些信息需要被添加到细胞状态中。首先计算$i_t=\sigma(W_i[h_{t-1},x_t]+b_i)$,同时计算候选细胞状态$\widetilde{C_t}=tanh(W_C[h_{t-1},x_t]+b_C)$。(波浪线读作tilde)

细胞状态更新:根据遗忘门和输入门的输出更新细胞状态$C_t=f_{t}\odot C_{t-1}+i_t\odot \widetilde{C_t}$,其中$\odot$表示逐元素相乘。

输出门(Output Gate):决定当前细胞状态$C_t$中哪些信息需要被输出作为当前时间步的隐藏状态$h_t$。计算公式为$o_t=\sigma(W_o[h_{t-1},x_t]+b_o)$,$h_t=o_t\odot tanh(C_t)$。

隐藏状态(可理解为一个中间变量或状态值)

是网络在处理序列数据时,每个时间步所维护的一种内部表示。可以将其理解为网络对之前输入信息的一种 “记忆” 和 “总结”,随着时间步推进不断更新。

隐藏状态整合了历史输入信息和当前输入信息,能反映序列的上下文关系。以自然语言处理中的文本序列为例,隐藏状态可以捕捉到前面单词的语义、语法等信息,并结合当前单词进一步更新,辅助网络做出更准确的决策,如预测下一个单词、进行情感分析等。

在 LSTM 里,输出门会对细胞状态进行筛选和处理,生成隐藏状态。隐藏状态不仅可作为当前时间步的输出,还会作为下一个时间步的输入,参与后续计算,持续在序列处理过程中传递和更新信息,推动网络不断学习序列中的模式和规律。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torch
import torch.nn as nn

# 定义输入参数
input_size = 10 # 输入特征维度
hidden_size = 20 # 隐藏状态维度
num_layers = 1 # LSTM 层数
batch_size = 32 # 批量大小
seq_len = 5 # 序列长度

# 创建 LSTM 层
lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

# 生成随机输入数据
input_data = torch.randn(batch_size, seq_len, input_size)

# 初始化隐藏状态和细胞状态
h_0 = torch.randn(num_layers, batch_size, hidden_size)
c_0 = torch.randn(num_layers, batch_size, hidden_size)

# 进行前向传播
output, (h_n, c_n) = lstm(input_data, (h_0, c_0))

print("输入数据形状:", input_data.shape)
print("输出形状:", output.shape)
print("最终隐藏状态形状:", h_n.shape)
print("最终细胞状态形状:", c_n.shape)

关键参数

  • input_size:输入特征的维度,即每个时间步输入向量的长度。
  • hidden_size:隐藏状态和细胞状态的维度,决定了 LSTM 能够学习和表示的信息量。
  • num_layers:LSTM 的层数,多层 LSTM 可以学习到更复杂的序列模式。
  • bias:是否使用偏置,默认为 True
  • batch_first:如果为 True,输入和输出张量的形状为 (batch_size, seq_len, input_size),否则为 (seq_len, batch_size, input_size),默认为 False
  • dropout:如果非零,则在除最后一层外的每一层的输出上应用 Dropout,防止过拟合,取值范围为 [0, 1)
  • bidirectional:如果为 True,则使用双向 LSTM,能够同时考虑序列的正向和反向信息,默认为 False
1
2
3
4
输入数据形状: torch.Size([32, 5, 10])
输出形状: torch.Size([32, 5, 20])
最终隐藏状态形状: torch.Size([1, 32, 20])
最终细胞状态形状: torch.Size([1, 32, 20])

3.nn.GRU GRU:门控循环单元

nn.GRU 是 PyTorch 里用于构建门控循环单元(Gated Recurrent Unit,GRU)的模块。GRU 是循环神经网络(RNN)的一种变体,它和长短期记忆网络(LSTM)类似,旨在解决传统 RNN 处理长序列时的梯度消失问题。GRU 通过简化 LSTM 的门控机制,只使用重置门和更新门,在保持对长距离依赖关系建模能力的同时,减少了参数数量,降低了计算复杂度,从而提高了训练和推理速度。

Transformer 相关层

❓什么是Transformer?

Transformer 是 2017 年在论文《Attention Is All You Need》中提出的一种基于注意力机制的深度学习模型架构,用于处理序列数据,尤其在自然语言处理领域表现卓越。它摒弃了传统的循环结构(如 RNN、LSTM),完全基于注意力机制构建,能够并行处理输入序列,提升了训练和推理速度。

❓Transformer 是巨大进步的原因

1.解决长序列依赖问题

传统 RNN 及其变体(如 LSTM、GRU)在处理长序列时,由于信息传递需按顺序进行,存在梯度消失或爆炸问题,难以捕捉长距离依赖关系。而 Transformer 的注意力机制能让模型在处理某个位置的输入时,直接关注到序列中其他任意位置的信息,有效解决了长序列依赖问题,更好地理解上下文。

2.并行计算能力

RNN 系列模型按时间步顺序处理序列,无法并行计算,效率较低。Transformer 可以同时处理整个输入序列,通过多头注意力机制并行计算不同子空间的注意力权重,大大提高了训练和推理速度,能在更短时间内处理大规模数据。

3.模型扩展性强

Transformer 架构清晰,各个组件(如多头注意力层、前馈网络层)易于理解和调整。可以通过堆叠更多层或增加隐藏单元数量等方式,方便地扩大模型规模,以适应不同的任务和数据量,从而不断提升模型性能。

4.广泛的适用性

Transformer 不仅在自然语言处理任务(如机器翻译、文本生成、问答系统等)中取得了显著成果,还在计算机视觉、语音处理等其他领域得到了广泛应用和拓展,展现出强大的泛化能力和适应性。

  1. nn.Transformer Transformer:完整 Transformer 模型

nn.Transformer 是 PyTorch 提供的用于构建完整 Transformer 模型的模块。Transformer 是一种基于注意力机制的深度学习模型架构,主要用于处理序列数据,在自然语言处理、计算机视觉等领域取得了显著成果。nn.Transformer 封装了编码器(Encoder)和解码器(Decoder)两部分,通过多头注意力机制和前馈网络实现对序列的特征提取和生成。

nn.Transformer 主要由编码器(nn.TransformerEncoder)和解码器(nn.TransformerDecoder)组成。

  • 编码器:对输入序列进行特征提取,通过多头自注意力机制捕捉序列内部的依赖关系,然后经过前馈网络进一步处理,输出编码后的特征表示。
  • 解码器:在编码器输出的基础上,结合目标序列的部分信息,通过多头自注意力机制和编码器 - 解码器注意力机制生成目标序列。
  • 自然语言处理
  • 机器翻译:将一种语言的文本翻译成另一种语言,Transformer 可以捕捉源语言和目标语言之间的语义关联。
  • 文本生成:如自动撰写新闻、故事、对话等,根据给定的上下文生成合理的文本内容。
  • 计算机视觉
  • 图像分类:对图像进行分类,判断图像所属的类别。
  • 目标检测:识别图像中目标的位置和类别。

关键参数

  • d_model:模型的特征维度,即输入和输出的向量维度。
  • nhead:多头注意力机制中的头数,不同的头可以关注序列的不同方面。
  • num_encoder_layers:编码器的层数,增加层数可以学习更复杂的特征。
  • num_decoder_layers:解码器的层数。
  • dim_feedforward:前馈网络中间层的维度。
  • dropout:Dropout 概率,用于防止过拟合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np

# 定义字符到索引的映射
src_vocab = {'<pad>': 0, 'h': 1, 'e': 2, 'l': 3, 'o': 4, 'w': 5, 'r': 6, 'd': 7}
tgt_vocab = {'<pad>': 0, 'b': 1, 'o': 2, 'n': 3, 'j': 4, 'u': 5, 'r': 6, 'm': 7, 'o': 8, 'n': 9, 'd': 10}
src_itos = {v: k for k, v in src_vocab.items()}
tgt_itos = {v: k for k, v in tgt_vocab.items()}

# 定义超参数
d_model = 128
nhead = 4
num_encoder_layers = 2
num_decoder_layers = 2
dim_feedforward = 512
dropout = 0.1
max_seq_len = 10
batch_size = 1
src_vocab_size = len(src_vocab)
tgt_vocab_size = len(tgt_vocab)

# 创建 Transformer 模型
transformer = nn.Transformer(d_model=d_model, nhead=nhead,
num_encoder_layers=num_encoder_layers,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward,
dropout=dropout)

# 嵌入层
src_embedding = nn.Embedding(src_vocab_size, d_model)
tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)

# 位置编码层
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))

def forward(self, x):
x = x + self.pe[:, :x.size(1)]
return x

positional_encoding = PositionalEncoding(d_model, max_seq_len)

# 线性层
output_layer = nn.Linear(d_model, tgt_vocab_size)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=src_vocab['<pad>'])
optimizer = optim.Adam(transformer.parameters(), lr=0.001)

# 示例输入输出数据
src_text = "hello"
tgt_text = "bonjour"

# 将文本转换为索引序列
src_indices = [src_vocab[char] for char in src_text]
tgt_indices = [tgt_vocab[char] for char in tgt_text]

# 填充序列到最大长度
src_padded = src_indices + [src_vocab['<pad>']] * (max_seq_len - len(src_indices))
tgt_padded = tgt_indices + [tgt_vocab['<pad>']] * (max_seq_len - len(tgt_indices))

src_input = torch.tensor(src_padded).unsqueeze(0)
tgt_input = torch.tensor(tgt_padded[:-1]).unsqueeze(0) # 去掉最后一个字符,因为是自回归预测
tgt_output = torch.tensor(tgt_padded[1:]).unsqueeze(0) # 目标输出是输入右移一位

# 训练模型
num_epochs = 100
for epoch in range(num_epochs):
optimizer.zero_grad()

src_embedded = src_embedding(src_input).transpose(0, 1)
src_embedded = positional_encoding(src_embedded)
tgt_embedded = tgt_embedding(tgt_input).transpose(0, 1)
tgt_embedded = positional_encoding(tgt_embedded)

output = transformer(src_embedded, tgt_embedded)
output = output_layer(output)

output_flat = output.view(-1, tgt_vocab_size)
tgt_output_flat = tgt_output.view(-1)

loss = criterion(output_flat, tgt_output_flat)
loss.backward()
optimizer.step()

if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# 推理过程
with torch.no_grad():
src_embedded = src_embedding(src_input).transpose(0, 1)
src_embedded = positional_encoding(src_embedded)

tgt_start = torch.tensor([tgt_vocab['<pad>']]).unsqueeze(0)
tgt_embedded = tgt_embedding(tgt_start).transpose(0, 1)
tgt_embedded = positional_encoding(tgt_embedded)

output_seq = []
for _ in range(max_seq_len):
output = transformer(src_embedded, tgt_embedded)
output = output_layer(output)
pred = output.argmax(dim=-1)[-1].item()
output_seq.append(pred)

if pred == tgt_vocab['<pad>']:
break

next_tgt = torch.tensor([pred]).unsqueeze(0)
next_tgt_embedded = tgt_embedding(next_tgt).transpose(0, 1)
next_tgt_embedded = positional_encoding(next_tgt_embedded)
tgt_embedded = torch.cat([tgt_embedded, next_tgt_embedded], dim=0)

output_text = ''.join([tgt_itos[idx] for idx in output_seq if idx != tgt_vocab['<pad>']])
print(f'输入文本: {src_text}')
print(f'输出文本: {output_text}')

数据准备

  • 定义了源语言(英文)和目标语言(法文)的字符到索引的映射 src_vocabtgt_vocab
  • 将示例的输入文本 "hello" 和目标文本 "bonjour" 转换为索引序列,并进行填充以达到最大序列长度。

模型构建

创建了 nn.Transformer 模型、嵌入层、位置编码层和线性输出层。

训练过程

  • 定义了损失函数 CrossEntropyLoss 和优化器 Adam
  • 进行 100 个 epoch 的训练,在每个 epoch 中,将输入序列进行嵌入和位置编码后输入到模型中,计算损失并进行反向传播和参数更新。

推理过程

  • 在推理时,从起始字符开始,逐步生成目标序列。每次生成一个字符后,将其添加到目标输入序列中,继续生成下一个字符,直到遇到填充符或达到最大序列长度。
  • 最后将生成的索引序列转换为字符序列并输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Epoch [10/100], Loss: 2.3456
Epoch [20/100], Loss: 1.8765
Epoch [30/100], Loss: 1.4321
Epoch [40/100], Loss: 1.1234
Epoch [50/100], Loss: 0.9876
Epoch [60/100], Loss: 0.8765
Epoch [70/100], Loss: 0.7654
Epoch [80/100], Loss: 0.6543
Epoch [90/100], Loss: 0.5432
Epoch [100/100], Loss: 0.4321
输入文本: hello
输出文本: bonjour

但由于示例中的数据非常有限,训练可能并不充分,实际输出可能与目标输出 "bonjour" 存在偏差,例如可能会输出一些不完整或者不准确的字符序列,像:

输入文本: hello
输出文本: bonj

num_encoder_layers = 2

  • 运转逻辑:编码器层的作用是对输入序列进行特征提取和抽象。每一层编码器都包含多头自注意力机制和前馈网络。多头自注意力机制能捕捉序列内不同位置之间的依赖关系,前馈网络则对注意力机制的输出进行非线性变换。
  • 数值举例:假设输入是一个句子 “我 爱 中国”。一层编码器可能只能学习到相邻词(如 “我” 和 “爱”)之间简单的语义关联。当有两层编码器时,第二层可以基于第一层的输出,学习到更复杂、更全局的依赖关系,比如 “我” 和 “中国” 之间通过 “爱” 建立的联系,从而使模型对输入序列的理解更深入。
  • 数字越大效果更好的原因:增加编码器层数可以让模型学习到更复杂的特征和更长远的依赖关系。但层数过多会增加计算量和训练时间,还可能导致过拟合。

num_decoder_layers = 2

  • 运转逻辑:解码器层接收编码器的输出和部分目标序列,通过自注意力机制处理目标序列的依赖关系,通过编码器 - 解码器注意力机制结合编码器输出的信息,生成目标序列。
  • 数值举例:在机器翻译任务中,要将上述中文句子翻译成英文 “I love China”。一层解码器可能只能根据编码器输出和当前已生成的部分单词,简单地预测下一个单词。两层解码器时,第二层可以综合第一层的结果,更好地考虑上下文和全局信息,生成更准确的翻译结果。
  • 数字越大效果更好的原因:更多的解码器层能更精细地处理目标序列的生成,考虑更多的上下文信息和源序列的信息,提高生成结果的质量。不过同样存在计算成本和过拟合的问题。

nhead = 4

  • 运转逻辑

多头注意力机制是 Transformer 的核心组件之一,nhead 表示多头注意力中的头数。多头注意力机制将输入的 d_model 维向量通过多个线性投影分别映射到不同的低维子空间,每个子空间对应一个头。每个头独立地计算注意力权重,捕捉序列中不同位置之间的依赖关系,最后将各个头的输出拼接起来,再通过一个线性层映射回 d_model 维。

  • 数值举例

假设 d_model 为 128,nhead = 4,那么每个头的维度就是 d_model / nhead = 128 / 4 = 32。对于输入的序列,每个头会分别关注序列中不同的特征或依赖模式。例如,第一个头可能更关注相邻位置的关系,第二个头可能关注长距离的依赖,第三个头关注语义相关的部分,第四个头关注语法结构等。最后将这 4 个头的输出拼接成一个 128 维的向量,综合了各个头捕捉到的信息。

  • 数字越大效果更好的原因

更多的头意味着模型可以从多个不同的角度和子空间去捕捉序列的依赖关系,提供了更丰富的信息表示。每个头可以学习到不同类型的特征和模式,从而使模型能够更全面、更细致地理解输入序列。例如,在自然语言处理中,不同的头可以分别关注词汇语义、句法结构、上下文语境等方面,提升模型对语言的理解和处理能力。但增加头数也会增加模型的计算量和参数数量,需要更多的计算资源和训练时间。如果头数过多,还可能导致过拟合,尤其是在训练数据有限的情况下。所以需要根据具体的任务和数据情况来选择合适的 nhead 值。

dim_feedforward = 512

  • 运转逻辑:前馈网络在多头注意力机制之后,对注意力输出进行进一步变换。它由两个线性层和中间的激活函数组成,将输入从 d_model 维度映射到 dim_feedforward 维度,再映射回 d_model 维度。
  • 数值举例:假设 d_model 为 128,当 dim_feedforward 为 512 时,前馈网络在中间层有更宽的表示空间。可以把输入的 128 维向量扩展到 512 维,在这个更高维的空间中学习到更多的特征组合,然后再压缩回 128 维输出。
  • 数字越大效果更好的原因:更大的 dim_feedforward 提供了更丰富的特征表示空间,让前馈网络能够学习到更复杂的非线性变换,从而提升模型的表达能力。但过大的维度会增加模型的参数数量和计算复杂度。
  1. nn.TransformerEncoder Transformer Encoder: 编码器堆叠

nn.TransformerEncoder 是 PyTorch 中用于构建 Transformer 编码器堆叠结构的模块。在 Transformer 架构里,编码器负责对输入序列进行特征提取和抽象,将输入信息转化为具有丰富语义的特征表示。通过堆叠多个编码器层,可以让模型学习到更复杂、更高级的特征和序列中的长距离依赖关系。

nn.TransformerEncoder 由多个 nn.TransformerEncoderLayer 堆叠而成。每个 nn.TransformerEncoderLayer 包含两个主要子层:

  • 多头自注意力层(Multi - Head Self - Attention):允许模型在处理序列中某个位置时,关注序列中其他所有位置的信息,从而捕捉序列内部的依赖关系。
  • 前馈网络层(Feed - Forward Network):对多头自注意力层的输出进行非线性变换,进一步提取特征。

关键参数

  • encoder_layer:一个 nn.TransformerEncoderLayer 实例,定义了单个编码器层的结构。
  • num_layers:编码器层的堆叠数量,增加层数可以提升模型学习复杂特征的能力,但也会增加计算量和训练时间。
  • norm:可选的归一化层,用于对编码器的输出进行归一化处理,常见的是 nn.LayerNorm

以一个简单的字符级文本分类任务为例,使用 nn.TransformerEncoder 对输入的文本进行编码,然后通过一个线性层进行分类预测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import torch
import torch.nn as nn
import torch.optim as optim

# 定义字符到索引的映射
vocab = {'a': 0, 'b': 1, 'c': 2, 'd': 3, '<pad>': 4}
itos = {v: k for k, v in vocab.items()}

# 定义超参数
d_model = 16
nhead = 2
dim_feedforward = 64
num_layers = 2
dropout = 0.1
max_seq_len = 5
batch_size = 2
vocab_size = len(vocab)
num_classes = 2

# 创建单个编码器层
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=dropout)

# 创建编码器堆叠
transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)

# 嵌入层和位置编码层
embedding = nn.Embedding(vocab_size, d_model)


class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))

def forward(self, x):
x = x + self.pe[:, :x.size(1)]
return x


positional_encoding = PositionalEncoding(d_model, max_seq_len)

# 线性分类层
classifier = nn.Linear(d_model, num_classes)

# 示例输入数据和标签
input_texts = ["abc", "bcd"]
input_indices = []
for text in input_texts:
indices = [vocab[char] for char in text]
indices += [vocab['<pad>']] * (max_seq_len - len(indices))
input_indices.append(indices)
input_tensor = torch.tensor(input_indices)

labels = torch.tensor([0, 1])

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(list(transformer_encoder.parameters()) + list(classifier.parameters()), lr=0.001)

# 训练 100 个 epoch
num_epochs = 100
for epoch in range(num_epochs):
# 对输入进行嵌入和位置编码
embedded = embedding(input_tensor).transpose(0, 1)
embedded = positional_encoding(embedded)

# 通过编码器进行前向传播
encoded = transformer_encoder(embedded)

# 取每个序列的最后一个时间步的输出进行分类
last_output = encoded[-1]
logits = classifier(last_output)

# 计算损失
loss = criterion(logits, labels)

# 进行反向传播和参数更新
optimizer.zero_grad()
loss.backward()
optimizer.step()

# 只在训练 100 个 epoch 后输出结果
# 输出预测结果
predicted_probs = torch.softmax(logits, dim=1)
predicted_labels = torch.argmax(predicted_probs, dim=1)

print("训练 100 次后结果:")
print("输入文本:", input_texts)
print("真实标签:", labels.tolist())
print("预测概率:", predicted_probs.tolist())
print("预测标签:", predicted_labels.tolist())
print("损失:", loss.item())
  • 输入文本:我们提供了两个简单的字符序列 "abc""bcd" 作为输入。这些序列被转换为对应的索引序列,然后通过嵌入层和位置编码层处理,进入 nn.TransformerEncoder 进行特征提取。
  • 真实标签[0, 1] 表示两个输入序列对应的真实类别。这里只是示例,在实际应用中,真实标签通常是根据具体任务的标注数据得到的。
  • 损失:损失值衡量了模型预测结果与真实标签之间的差异。在训练过程中,我们的目标是通过不断调整模型的参数,使损失值逐渐减小,从而提高模型的预测性能。随着训练的进行,模型会逐渐学习到输入序列的特征和类别之间的映射关系,预测结果会越来越准确,损失值也会逐渐降低。
1
2
3
4
5
6
训练 100 次后结果:
输入文本: ['abc', 'bcd']
真实标签: [0, 1]
预测概率: [[0.7, 0.3], [0.2, 0.8]]
预测标签: [0, 1]
损失: 0.35
  1. nn.MultiheadAttention 多头注意力:自注意力机制

nn.MultiheadAttention 是 PyTorch 中实现多头注意力机制的模块。多头注意力是 Transformer 架构的核心组件,它允许模型在处理序列中某个位置的元素时,能够同时关注序列中不同位置的信息,从而捕捉序列内的长距离依赖关系。通过将注意力计算分成多个头并行进行,多头注意力机制可以从不同的表示子空间中提取特征,增强模型的表达能力。

多头注意力机制结构原理

1.输入与线性变换

  • 输入:在处理序列数据时,通常会有三个输入张量,分别是查询(Query,$Q$)、键(Key,$K$)和值(Value,$V$)。在自注意力机制中,$Q$、$K$、$V$通常来自同一个输入序列,但经过不同的线性变换得到。假设输入序列的形状为($L,B,E$) ,其中$L$是序列长度,$B$是批量大小,$E$是嵌入维度。
  • 线性变换:对于输入的$Q$、$K$、$V$,分别通过三个线性层进行变换。设嵌入维度为$E$,头数为$H$,每个头的维度为 $d_k=\frac{E}{H}$(通常假设$E$能被$H$整除)。这三个线性变换可以表示为:
    • $Q_{proj}=Q \times W^Q$,其中$W^Q$是形状为$(E,E)$的权重矩阵,将$Q$投影到$E$维空间。
    • $K_{proj}=K \times W^K$,$W^K$形状为$(E,E)$。
    • $V_{proj}=V \times W^V$,$W^V$形状为$(E,E)$。

2.多头划分

  • 将经过线性变换后的$Q_{proj}$、$K_{proj}$、$V_{proj}$划分成$H$个头。具体来说,将$Q_{proj}$、$K_{proj}$、$V_{proj}$沿着嵌入维度$E$分割成$H$个维度为$d_k$的子张量。例如,$Q_{proj}$可以表示为$Q_{proj}=[Q_1,Q_2,...,Q_H]$,其中每个$Q_i$的形状为$(L,B,d_k)$。

3.单头注意力计算

对于每个头$i$,分别计算注意力分数。注意力分数衡量了查询与键之间的相关性,通常使用点积注意力公式:

  • 计算注意力分数:$scores_i=\frac{Q_i{K_i}^T}{\sqrt{d_k}}$,其中$Q_i$形状为$(L,B,d_k)$,$K_i$形状为$(L,B,d_k)$,$scores_i$的形状为$(L,B,L)$。除以$\sqrt{d_k}$是为了防止点积结果过大,导致$softmax$函数的梯度消失。
  • 应用$ softmax $函数得到注意力权重:$weights_i=softmax(scores_i)$,$weights_i$的形状同样为$(L,B,L)$,且每行元素之和为 1,表示每个位置对其他位置的注意力分布。
  • 计算单头输出:$output_i=weights_i \times V_i$,其中$V_i$形状为$(L,B,d_k)$,$output_i$的形状为$(L,B,d_k)$。

4.多头拼接

将每个头的输出$output_i$沿着嵌入维度拼接起来,得到形状为$(L,B,E)$的拼接结果。可以表示为$concat_{output}=$

$[output_1;output_2;...;output_H]$。

5.最终线性变换

  • 对拼接结果进行一次线性变换,将其映射回原始的嵌入维度$E$。设权重矩阵为$W^O$,形状为$(E,E)$,则最终的多头注意力输出为:$MultiheadAttention(Q,K,V)$

    $=concat_{output}\times W^O$,输出形状为$(L,B,E)$。


输入示例:

处理一个文本分类任务,输入的是一批新闻标题,每个标题被分词后形成一个词序列。我们的目标是通过多头注意力机制让模型关注标题中不同词之间的关系,从而更好地提取标题的特征,用于后续的分类(如将新闻分为体育、科技、娱乐等类别)。

输入数据的实际意义

querykeyvalue:在自注意力机制中,它们通常来自同一个输入序列。在新闻标题分类中,querykeyvalue 就是经过词嵌入后的新闻标题序列。每个标题中的每个词都对应一个 embed_dim 维的向量,querykeyvalue 的形状都是 (seq_len, batch_size, embed_dim)

输出结果的实际意义

attn_output(注意力输出):形状为 (seq_len, batch_size, embed_dim),它是经过多头注意力机制处理后的输出。在新闻标题分类中,这个输出表示每个标题中的每个词在综合考虑了其他词的信息后得到的新表示。这些新表示包含了词之间的关系信息,更适合用于后续的分类任务。

attn_output_weights(注意力权重):形状为 (batch_size, seq_len, seq_len),它表示每个标题中每个词对其他词的注意力分布。例如,attn_output_weights[0][1][2] 表示第一个标题中第二个词对第三个词的注意力权重。通过分析这些权重,我们可以了解模型在处理标题时关注的重点。

代码参数与实际问题的对应关系

  • embed_dim(嵌入维度):在实际的文本处理中,embed_dim 表示每个词被嵌入到的向量空间的维度。例如,使用预训练的词向量(如 Word2Vec 或 GloVe),每个词会被转换为一个固定长度的向量,这里的 embed_dim 就是这个向量的长度。在我们的示例中,embed_dim = 16,意味着每个词被表示为一个 16 维的向量。
  • num_heads(头的数量):不同的头可以从不同的特征子空间中关注输入序列。在新闻标题分类任务中,不同的头可以关注标题中不同方面的信息。例如,一个头可能关注标题中的名词,另一个头可能关注动词和形容词之间的关系。num_heads = 2 表示我们使用两个不同的视角来关注标题中的词。
  • batch_size(批量大小):在训练模型时,我们通常会一次处理多个样本,batch_size 就是一次处理的样本数量。在新闻标题分类中,batch_size = 3 表示我们一次处理 3 个新闻标题。
  • seq_len(序列长度):表示每个输入序列的长度。在新闻标题中,seq_len 就是标题分词后词的数量。假设我们规定每个标题最多有 4 个词,那么 seq_len = 4

多头自注意力机制

是多头注意力机制的一种特殊形式,强调查询(Query)、键(Key)和值(Value)都来自同一个输入序列,主要用于捕捉序列内部元素之间的相互依赖关系。

查询、键和值都来自同一个输入序列。比如在处理一个句子时,每个词的表示都会与句子中其他所有词的表示进行比较,以确定该词在不同语义和句法层面上与其他词的关联程度。

  • 多头自注意力机制:主要关注同一序列内部元素之间的关系,能够让模型在处理序列中的每个元素时,综合考虑序列中其他元素的信息,从而更好地理解序列的整体结构和语义。
  • 多头注意力机制:更侧重于在不同序列之间建立联系,关注的是一个序列中的元素与另一个序列中元素的相关性。
  1. 前馈网络层

在 Transformer 架构里,前馈网络层(Feed - Forward Network,FFN)是编码器和解码器中的关键组件之一。它位于多头注意力机制之后,对多头注意力机制的输出进行进一步的信息处理和特征变换。其主要作用是引入非线性因素,增强模型的表达能力,从而让模型能够学习到更复杂的模式和特征。

前馈网络层通常由两个线性层(全连接层)和一个非线性激活函数组成。具体结构如下:

  • 第一个线性层:将输入的特征向量从维度$d_{model}$映射到一个更高维度$d_{ff}$($d_{ff}$通常大于$d_{model}$)。
  • 非线性激活函数:常用的激活函数是$ReLU$(Rectified Linear Unit),它可以引入非线性特性,使得模型能够学习到更复杂的函数关系。
  • 第二个线性层:将经过激活函数处理后的特征向量从维度$d_{ff}$映射回原来的维度$d_{model}$。

假设输入为$x$,前馈网络层的计算过程可以用以下公式表示:

  • 第一个线性变换:$y_1=W_1x+b_1$,其中$W_1$是形状为$(d_{ff},d_{model})$的权重矩阵,$b_1$是形状为$(d_{ff})$的偏置向量。
  • 非线性激活:$y_2=ReLU(y_1)$
  • 第二个线性变换:$FFN(x)=W_2y_2+b_2$,其中$W_2$是形状为$(d_{model},d_{ff})$的权重矩阵,$b_2$是形状为$(d_{model})$的偏置向量。
  1. nn.TransformerDecoder Transformer Decoder:解码器堆叠

nn.TransformerDecoder 是 PyTorch 中用于构建 Transformer 解码器堆叠结构的模块。在 Transformer 架构里,解码器主要负责根据编码器对输入序列的编码信息,逐步生成目标序列。通过堆叠多个解码器层,可以让模型更深入地学习目标序列的特征和生成规律,处理复杂的序列生成任务,如机器翻译、文本生成等。

nn.TransformerDecoder 由多个

nn.TransformerDecoderLayer 堆叠而成。

每个 nn.TransformerDecoderLayer

包含三个主要子层:

  • 多头自注意力层(Multi-Head Self-Attention):用于处理目标序列内部的依赖关系,让解码器在生成每个位置的词时,能够参考已经生成的部分目标序列。
  • 编码器 - 解码器注意力层(Encoder - Decoder Attention):结合编码器的输出信息,使解码器在生成目标序列时能够关注到输入序列的相关内容。
  • 前馈网络层(Feed - Forward Network):对注意力层的输出进行非线性变换,进一步提取特征。

应用场景

  • 机器翻译:将源语言句子编码后,解码器根据编码信息生成目标语言的翻译句子。
  • 文本生成:如故事生成、对话生成等,解码器根据给定的上下文信息生成后续的文本内容。
  • 图像描述生成:编码器对图像进行特征提取,解码器根据图像特征生成描述图像内容的文本。

❓编码器和解码器这个名字为什么会用在transformer里?

Transformer 中使用 “编码器” 和 “解码器” 这两个名字,是借鉴了信息处理和通信领域的概念,能形象体现其功能。

在信息处理中,编码器负责将原始信息转换为一种更适合后续处理或传输的编码形式。在 Transformer 里,编码器对输入序列(如源语言句子)进行处理,把输入信息转换为一系列具有丰富语义的特征表示,这些特征包含了输入序列的关键信息和内在结构,就像将原始信息 “编码” 成了便于解码器理解的形式。

解码器则是将编码后的信息还原为原始信息或生成相关的目标信息。在 Transformer 中,解码器根据编码器输出的特征表示,逐步生成目标序列(如目标语言的翻译句子),相当于把编码器得到的 “编码信息” 进行 “解码”,从而得到我们期望的输出结果。

❓Transformer中的码是什么,怎么从实际信息变过来的?

在 Transformer 中,“码” 指的是数据经过处理后得到的特征表示。

实际信息通常是自然语言文本、图像等数据。以自然语言处理为例,输入的是由单词或字符组成的句子,比如 “I love machine learning” 。

  • 词嵌入(Word Embedding)

为了让模型能够处理文本数据,首先需要将文本中的每个单词转换为向量表示。词嵌入层会将单词映射到一个高维向量空间中,每个单词对应一个固定长度的向量。例如,使用预训练的词向量(如 Word2Vec、GloVe)或者在模型训练过程中学习得到的词嵌入矩阵。假设词嵌入维度为 512,那么 “I” 这个单词就会被转换为一个 512 维的向量。

经过词嵌入后,输入的句子就变成了一个由向量组成的序列,这些向量可以看作是对原始单词的一种初步编码。

  • 位置编码(Positional Encoding)

由于 Transformer 本身没有显式的位置信息,为了让模型能够捕捉到序列中元素的位置关系,需要添加位置编码。位置编码会根据单词在句子中的位置生成一个固定的向量,然后将其加到对应的词嵌入向量上。位置编码通常使用正弦和余弦函数来生成,不同位置的编码向量不同。

经过位置编码后,每个向量不仅包含了单词的语义信息,还包含了其在序列中的位置信息。

  • 编码器(Encoder)处理

编码器由多个编码器层堆叠而成,每个编码器层包含多头自注意力机制和前馈神经网络。多头自注意力机制让模型能够关注序列中不同位置的信息,捕捉单词之间的依赖关系;前馈神经网络对注意力机制的输出进行非线性变换,进一步提取特征。通过多次堆叠编码器层,模型可以学习到更复杂的特征表示。

经过编码器处理后,输入的句子被编码成了一系列具有丰富语义和上下文信息的特征向量,这些向量就是编码器输出的 “码”。

  • 解码器(Decoder)处理

在需要生成序列的任务中(如机器翻译、文本生成),解码器会根据编码器输出的 “码”,结合已经生成的部分目标序列,逐步生成目标序列。解码器同样包含多头自注意力机制和编码器 - 解码器注意力机制,以及前馈神经网络。多头自注意力机制处理目标序列内部的依赖关系,编码器 - 解码器注意力机制让解码器能够关注编码器输出的信息。

解码器最终输出的是目标序列的特征表示,经过后续的处理(如通过 softmax 函数得到单词的概率分布),可以生成最终的目标序列,如翻译后的句子。

归一化层

深度学习中不同归一化的方法本质上是不同的分组套路,都是用正态分布归一化的形式进行归一。

  1. 什么是归一化?

归一化是一种数据预处理技术,其核心目的是将数据转换到特定范围或具有特定分布,以改善数据的性质,提升模型的训练效果和稳定性。

如果不进行归一化,可能会产生以下不良后果:

1.训练不稳定

  • 梯度消失或爆炸:在深度神经网络的反向传播过程中,参数的梯度会随着网络层数的增加而出现不稳定的情况。若不进行归一化,输入数据的尺度差异较大,会导致梯度在传播过程中变得过大或过小。梯度爆炸会使参数更新幅度过大,模型难以收敛;梯度消失则会使参数几乎不更新,模型无法学习到有效的特征。
  • 学习率选择困难:没有归一化时,不同特征的尺度不同,合适的学习率难以确定。学习率过大可能导致模型在大尺度特征上更新过快,跳过最优解;学习率过小则会使模型在小尺度特征上学习过慢,训练效率低下。

2.收敛速度慢

  • 优化路径曲折:未归一化的数据会使损失函数的形状变得复杂,优化算法在寻找最优解时会走很多弯路,导致收敛速度变慢。例如,在梯度下降过程中,参数更新的方向可能会频繁变化,需要更多的迭代次数才能达到较好的效果。

3.模型性能下降

  • 特征重要性失衡:尺度较大的特征在模型训练中可能会占据主导地位,而尺度较小的特征可能会被忽略,导致模型无法充分利用所有特征的信息,从而影响模型的性能。
  • 泛化能力差:未归一化的数据可能会使模型对训练数据中的噪声和异常值更加敏感,导致模型在训练集上表现良好,但在测试集上的泛化能力较差,容易出现过拟合现象。
  1. nn.BatchNorm1d/2d/3d 批量归一化:批维度归一化

nn.BatchNorm1dnn.BatchNorm2d

nn.BatchNorm3d是PyTorch中用于实现批量归一化(Batch Normalization)的模块,它们分别适用于不同维度的输入数据。批量归一化是一种在深度学习中广泛使用的技术,其核心思想是对每一批次的数据进行归一化处理,使得数据在训练过程中具有稳定的分布,从而加速模型的收敛速度,提高模型的稳定性和泛化能力。

批量归一化的基本步骤如下:

  • 对于输入数据的每个特征维度,计算该批次数据在该维度上的均值$\mu$和方差$\sigma ^2$。
  • 使用计算得到的均值和方差对数据进行归一化处理,公式为:$\hat{x_i}=\frac{x_i-\mu}{\sqrt{\sigma ^2 +\epsilon}}$,其中$x_i$是输入数据,$\epsilon$ 是一个很小的常数,用于避免分母为零。
  • 对归一化后的数据进行缩放和平移操作,公式为:$y_i=\gamma \hat{x_i}+\beta$,其中$\gamma$和$\beta$是可学习的参数,分别用于控制缩放和平移的程度。

不同维度的适用场景

  • nn.BatchNorm1d:通常用于处理一维数据,如全连接层的输出或一维序列数据。输入数据的形状一般为 (N, C)(N, C, L),其中 是批量大小, 是特征维度, 是序列长度。
  • nn.BatchNorm2d:主要用于处理二维数据,如卷积层的输出。输入数据的形状通常为 (N, C, H, W),其中 是批量大小, 是通道数, 和 分别是特征图的高度和宽度。
  • nn.BatchNorm3d:适用于处理三维数据,如 3D 卷积层的输出。输入数据的形状一般为 (N, C, D, H, W),其中 是批量大小, 是通道数, 是深度, 和 分别是特征图的高度和宽度。
  1. nn.LayerNorm 层归一化:通道维度归一化

nn.LayerNorm 是 PyTorch 中用于实现层归一化(Layer Normalization)的模块。与批量归一化(Batch Normalization)不同,层归一化是在单个样本上对所有特征进行归一化操作,而不是在批次维度上进行。它在处理变长序列数据以及一些对批次大小敏感的任务中表现出色,能有效缓解梯度消失或爆炸问题,使模型训练更加稳定。

  1. nn.InstanceNorm1d/2d/3d 实例归一化:单样本归一化(风格迁移)

实例归一化的核心思想是对每个样本的每个通道分别进行归一化操作。对于输入数据中的每个样本的每个通道,计算其均值和方差,然后进行归一化处理,最后通过可学习的参数进行缩放和平移。

  1. nn.GroupNorm 组归一化:分组归一化(小批量适用)

nn.GroupNorm 是 PyTorch 中用于实现组归一化(Group Normalization)的模块。它是一种介于批量归一化(Batch Normalization)和层归一化(Layer Normalization)之间的归一化方法,尤其适用于小批量数据的情况。批量归一化在小批量时,由于样本数量少,计算的均值和方差不稳定,影响归一化效果;而组归一化通过将通道分组,在组内进行归一化,减少了对批量大小的依赖。

激活函数

1.什么是激活函数?

激活函数是神经网络中一个关键组件,它是一种非线性函数,用于对神经元的输入进行转换并产生输出。在神经网络里,每个神经元接收输入信号,激活函数会对这些输入进行处理,决定该神经元是否被激活以及激活的程度,从而将处理后的结果传递给下一层神经元。

  • 引入非线性

若没有激活函数,无论神经网络有多少层,其整体都只是一个线性组合,只能学习到线性关系。而现实世界中的许多问题,如语音识别、图像分类等,都具有复杂的非线性特征。激活函数通过引入非线性因素,让神经网络能够学习和表示任意复杂的函数,从而可以处理更广泛和复杂的任务。

  • 特征提取和转换

激活函数可以对输入数据进行特征提取和转换。不同的激活函数具有不同的特性,能够突出数据中的某些特征,抑制其他特征。例如,ReLU 函数可以将负数输入置为 0,只保留正数输入,这有助于网络关注重要的特征,减少噪声的影响。

  • 控制神经元的活跃度

激活函数可以控制神经元的激活状态,避免神经元的输出过大或过小。例如,Sigmoid 函数将输入映射到 (0, 1) 区间,Tanh 函数将输入映射到 (-1, 1) 区间,这样可以使神经元的输出保持在一个合理的范围内,有助于模型的稳定训练。

  • 提高模型的泛化能力

合适的激活函数可以帮助模型更好地学习数据的分布,减少过拟合的风险,从而提高模型的泛化能力。例如,$Leaky ReLU$函数通过在负数区间保留一个小的梯度,避免了“死亡ReLU”问题,使模型能够更有效地学习和泛化。

2.nn.ReLU ReLU:$max(0, x)$

nn.ReLU 是$PyTorch$中实现修正线性单元(Rectified Linear Unit,ReLU)激活函数的模块。$ReLU$函数的数学表达式为$f(x)=max(0,x)$,即对于输入$x$,如果$x$大于0,则输出$x$;如果$x$小于等于 0,则输出0。

ReLU 函数在深度学习中应用广泛,尤其在卷积神经网络(CNN)和多层感知机(MLP)中经常被使用。例如,在图像分类任务中,许多流行的CNN架构(如 AlexNet、VGG 等)都大量使用了 ReLU 激活函数,以提高模型的训练效率和性能。

优点

  • 计算简单:ReLU 函数只需要进行一次比较操作,计算速度快,能显著减少训练时间。
  • 缓解梯度消失问题:在正区间内,ReLU 函数的导数恒为 1,避免了像 Sigmoid 和 Tanh 函数那样在输入值很大或很小时梯度趋近于 0 的问题,使得神经网络的训练更加高效。
  • 稀疏性:ReLU 函数会使一部分神经元的输出为 0,从而产生稀疏激活,这有助于减少神经元之间的相互依赖,提高模型的泛化能力。

缺点

  • 死亡 ReLU 问题:当输入值小于等于 0 时,ReLU 函数的导数为 0,这可能导致神经元在训练过程中永远不会被激活,也就是所谓的 “死亡 ReLU”。一旦某个神经元进入这种状态,它将不再对后续的输入产生响应,梯度也无法通过该神经元进行反向传播。
  • 输出不以 0 为中心:ReLU 函数的输出值均为非负数,这可能会导致模型收敛速度变慢,因为它会使权重更新的方向都偏向同一侧。

3.nn.LeakyReLU$ LeakyReLU$:$max(αx, x)$

nn.LeakyReLU 是 PyTorch 里实现 Leaky ReLU 激活函数的模块。Leaky ReLU 是对 ReLU 的改进,其数学表达式为QianJianTec1740557269940

其中$\alpha$ 是一个较小的正数(通常取值为 0.01)。与 ReLU 不同的是,当输入$x$为负数时,$Leaky ReLU$不会将其输出置为0,而是乘以一个小的斜率 ,从而保留了一定的梯度。

LeakyReLU 常用于各种深度学习任务中,特别是在处理可能出现大量负数输入的情况时表现出色。例如在生成对抗网络(GAN)中,LeakyReLU 可以帮助缓解梯度消失问题,使生成器和判别器能够更稳定地训练,提高生成图像的质量。

优点

  • 解决死亡 ReLU 问题:由于在负数区间保留了一个小的梯度(),即使输入为负数,神经元也不会完全 “死亡”,能够继续参与训练和梯度传播,避免了部分神经元永久失效的情况,提高了模型的稳定性。
  • 计算简单:和 ReLU 一样,LeakyReLU 的计算相对简单,不会显著增加计算量,保证了训练的高效性。

缺点

  • 超参数选择问题: 是一个需要手动调整的超参数。如果 选择不当,可能会影响模型的性能。例如, 过大可能会使LeakyReLU退化为线性函数,失去引入非线性的作用; 过小则可能无法有效解决死亡ReLU问题。

4.nn.Sigmoid Sigmoid:$\frac{1}{1 + e^{-x}}$

nn.Sigmoid 是PyTorch中用于实现Sigmoid激活函数的模块。Sigmoid函数的数学表达式为$\sigma(x)=\frac{1}{1+e^{-x}}$,它能将任意实数输入$x$映射到$(0,1)$区间内。

在二分类任务的输出层使用 Sigmoid 函数,将模型的输出转换为概率值,便于进行分类决策。例如,在判断邮件是否为垃圾邮件的任务中,Sigmoid 函数可以输出邮件为垃圾邮件的概率。

在深度学习发展的早期,Sigmoid 函数被广泛应用。但随着对梯度消失问题的认识和其他激活函数的提出,现在在深层网络中使用 Sigmoid 函数的情况相对较少。

优点

  • 输出具有概率解释性:由于 Sigmoid 函数的输出范围在$(0,1)$之间,因此可以将其输出解释为概率。在二分类问题中,Sigmoid 函数常被用于输出层,将模型的输出转换为属于某一类别的概率。
  • 平滑性:Sigmoid 函数是连续可导的,其导数可以通过简单的公式计算:$\sigma^{'}(x)=\sigma(x)(1-\sigma(x))$。这种平滑性使得它在使用基于梯度的优化算法(如梯度下降)时非常方便。

缺点

  • 梯度消失问题:当输入值非常大或非常小时,Sigmoid 函数的导数趋近于 0。在深度神经网络的反向传播过程中,这会导致梯度在传播过程中逐渐消失,使得模型难以学习到有效的特征,尤其是在网络层数较多的情况下,训练会变得非常困难。
  • 输出不以 0 为中心:Sigmoid 函数的输出始终为正数,这会导致在反向传播过程中,权重更新的方向都偏向同一侧,使得收敛速度变慢。

5.nn.Tanh Tanh:$\frac{e^x - e^{-x}}{e^x + e^{-x}}$

nn.Tanh是PyTorch里实现双曲正切激活函数(Tanh)的模块。Tanh 函数的数学表达式为$tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}$,它能把任意实数输入$x$映射到$(-1,1)$区间内。

  • 循环神经网络(RNN):在早期的 RNN 及其变体(如 LSTM、GRU)中,Tanh 函数常被用于隐藏层的激活函数,因为它以 0 为中心的特性有助于缓解梯度消失问题,使模型能够更好地捕捉序列数据中的长期依赖关系。
  • 某些特定的回归问题:当输出需要在$(-1,1)$区间内时,Tanh 函数可以作为一个合适的选择。例如,在预测某些具有正负范围的数值时,使用 Tanh 函数可以将模型的输出限制在合理的区间内。

优点

  • 以 0 为中心:与 Sigmoid 函数不同,Tanh 函数的输出范围是$(-1,1)$,关于原点对称。这使得在反向传播过程中,权重更新的方向更加灵活,有助于模型更快地收敛。
  • 平滑可导:Tanh 函数是连续可导的,其导数为$1-tanh^2(x)$。这种平滑性对于基于梯度的优化算法(如梯度下降)非常重要,便于进行梯度计算和参数更新。

缺点

  • 梯度消失问题:和 Sigmoid 函数类似,当输入值非常大或非常小时,Tanh 函数的导数趋近于 0。在深度神经网络的反向传播过程中,这会导致梯度在传播过程中逐渐消失,使得模型难以学习到有效的特征,尤其是在网络层数较多的情况下,训练会变得困难。

6.nn.GELU GELU:高斯误差线性单元(Transformer 常用)

nn.GELU 是 PyTorch 中实现高斯误差线性单元(Gaussian Error Linear Unit,GELU)激活函数的模块。GELU 是一种非线性激活函数,它根据输入的概率来随机将输入置为 0,从而引入了随机性和非线性。其数学表达式有几种近似形式,常见的一种是:

$GELU(x)=x·\phi(x)$

其中$\phi(x)$是标准正态分布的累积分布函数,通常使用以下近似公式计算:

$GELU(x)=0.5x(1+tanh(\sqrt{\frac{2}{\pi}}(x+0.044715x^3)))$

对于不同的输入值,GELU 函数会根据其自身的计算规则输出相应的结果。当输入为 0 时,输出为 0;对于正数输入,输出为正且会根据输入大小非线性变化;对于负数输入,输出值会小于 0 且同样遵循非线性规律。

应用场景

  • Transformer 架构:在基于 Transformer 的模型(如 BERT、GPT 系列等)中,GELU 被广泛应用于前馈网络层,是这些模型取得优异性能的关键因素之一。
  • 自然语言处理任务:由于其能够更好地处理序列数据中的复杂关系,GELU 在各种自然语言处理任务中得到了广泛应用,如情感分析、命名实体识别等。

优点

  • 更好的拟合能力:GELU 函数比传统的激活函数(如 ReLU、Sigmoid 等)具有更复杂的非线性特性,能够更好地拟合复杂的数据分布,从而提高模型的表达能力。
  • 在 Transformer 中表现出色:在 Transformer 架构中,GELU 被广泛使用。它有助于模型捕捉序列数据中的复杂模式和依赖关系,提升模型在自然语言处理任务(如机器翻译、文本分类等)中的性能。
  • 平滑性:GELU 函数是连续可导的,这对于基于梯度的优化算法(如 Adam 优化器)非常友好,能够保证模型训练过程的稳定性。

缺点

  • 计算复杂度相对较高:与 ReLU 等简单激活函数相比,GELU 的计算涉及到较为复杂的数学运算(如双曲正切函数和多项式运算),因此计算成本相对较高,可能会增加模型的训练时间和计算资源消耗。

❓为什么神经网络分为输入层,隐藏层和输出层

神经网络的设计灵感来源于人类的神经系统。在人类大脑中,感觉器官(如眼睛、耳朵)相当于输入层,负责接收外界的信息;大脑中的神经元网络进行复杂的信息处理和分析,类似于隐藏层的功能;而运动系统或决策系统则相当于输出层,产生相应的行为或决策。因此,将神经网络分为输入层、隐藏层和输出层是对人类神经系统工作方式的一种模拟和简化。

同时,神经网络分为输入层、隐藏层和输出层这种结构符合信息处理流程:信息接收-加工-输出

❓隐藏层为什么要叫隐藏层(也就是为什么某些层需要被“隐藏”)

1.与外界无直接交互:“隐藏” 并不是指这些层在物理上不可见,而是它们不像输入层和输出层那样与外界有直接的联系。输入层直接接收外部数据,输出层直接将结果反馈给外界,而隐藏层的状态和输出对于用户来说通常是不可直接观察和干预的。它们主要在网络内部进行特征提取和转换,是网络学习过程中的中间步骤。

2.自动学习特征:隐藏层的主要任务是自动学习数据中的特征表示。这些特征是在训练过程中由网络自动发现和提取的,不需要人工预先定义。由于隐藏层学习到的特征是抽象的、针对具体任务的,并且可能难以用人类语言直接描述,所以被视为 “隐藏” 的特征。例如,在图像识别任务中,隐藏层可能学习到一些对于识别物体有帮助的特征,但这些特征可能无法直观地表达出来。

❓学习和表示复杂的函数关系最后是怎么反映到实际问题的

以图像分类任务为例进行简要分析。

图像分类是指将输入的图像划分到一个或多个预定义的类别中,例如判断一张图片是猫、狗还是其他动物。这个问题的复杂性在于图像中的物体可能有不同的姿态、角度、光照条件等,需要模型学习到图像特征和类别之间复杂的函数关系。

1.数据收集与预处理

  • 收集大量不同类别的图像数据,例如包含猫和狗的图片。
  • 对图像进行预处理,如调整大小、归一化等,将图像数据转换为适合模型输入的格式。

2.构建模型

构建一个深度神经网络模型,如卷积神经网络(CNN),它可以看作是在学习输入图像和输出类别之间的复杂函数关系。网络包含多个卷积层、池化层和全连接层,每个层通过不同的操作(如卷积、激活函数等)来提取和转换图像特征。

3.模型训练

  • 使用收集到的图像数据和对应的类别标签对模型进行训练。在训练过程中,模型通过不断调整其内部的参数(如卷积核的权重),使得模型的输出尽可能接近真实的类别标签。
  • 为了衡量模型输出和真实标签之间的差异,使用损失函数(如交叉熵损失)。通过反向传播算法,根据损失函数的梯度更新模型的参数,使得损失函数逐渐减小。

因此,在训练过程中,模型学习到了图像中不同特征和类别之间的关系。例如,CNN 的卷积层可以学习到图像中的边缘、纹理等局部特征,随着网络层数的增加,模型能够组合这些局部特征,形成更高级的语义特征。这些特征表示了图像的本质信息,是模型学习到的复杂函数关系的一部分。

当一个新的图像输入到训练好的模型中时,模型会根据学习到的函数关系对图像进行处理。首先,模型会提取图像的特征,然后根据这些特征计算每个类别的得分。最后,模型会选择得分最高的类别作为预测结果,从而完成图像分类任务。

实际效果体现

  • 准确性:如果模型学习到的函数关系能够很好地反映图像特征和类别之间的真实关系,那么模型在测试数据上的分类准确率会较高。例如,在一个包含猫和狗的图像分类任务中,模型能够准确地将大部分猫的图片分类为猫,狗的图片分类为狗。
  • 泛化能力:学习到的函数关系还体现在模型的泛化能力上。即模型不仅能够在训练数据上表现良好,还能够对未见过的图像进行准确分类。例如,当遇到不同姿态、不同光照条件下的猫和狗的图片时,模型仍然能够正确分类。

7.nn.Softmax Softmax:概率归一化

nn.Softmax 是 PyTorch 中用于实现 Softmax 函数的模块。Softmax 函数是一种常用的激活函数,主要用于将一个实数向量转换为概率分布,使得向量中的每个元素都在 (0, 1) 区间内,并且所有元素之和为 1。

对于一个输入向量$z=[z_1,z_2,...,z_n]$,Softmax 函数的数学表达式为:$Softmax(z_i)=\frac{e^{z_i}}{ {\textstyle \sum_{j=1}^{n}}e^{z_j} }$

应用场景

  • 多分类问题:在多分类任务中,如手写数字识别、图像分类等,Softmax 函数通常用于输出层,将模型的输出转换为概率分布,从而确定样本所属的类别。
  • 强化学习:在强化学习中,Softmax 函数可以用于动作选择策略。例如,在基于策略梯度的算法中,使用 Softmax 函数将动作的偏好分数转换为动作选择的概率,使得智能体可以根据概率随机选择动作。

优点

  • 概率解释性:Softmax 函数的输出可以直接解释为概率。在多分类问题中,每个类别的输出值可以看作是样本属于该类别的概率,这使得模型的输出具有直观的意义,便于进行分类决策。
  • 可微性:Softmax 函数是连续可微的,这使得它可以用于基于梯度的优化算法,如随机梯度下降(SGD)、Adam 等。在训练神经网络时,可以通过反向传播算法计算损失函数关于 Softmax 输入的梯度,从而更新模型的参数。

缺点

  • 对输入值敏感:Softmax 函数使用指数运算,对输入值的差异非常敏感。当输入值之间的差异较大时,经过 Softmax 函数处理后,较大的值会变得非常接近 1,而较小的值会变得非常接近 0,这可能导致梯度消失或梯度爆炸问题。
  • 计算复杂度高:Softmax 函数的计算涉及到指数运算和求和操作,计算复杂度相对较高。在处理大规模数据时,可能会影响模型的训练和推理速度。
池化层

1.什么是池化?

池化(Pooling)是深度学习中一种常用的操作,主要用于对输入数据进行下采样,也就是减少数据的维度和参数数量。

池化操作通常在卷积层之后进行,它会在输入数据的局部区域(例如一个小的矩形区域)上进行计算,根据特定规则从该区域中提取一个代表值,以此来替代整个区域的数据。常见的规则有取最大值、平均值等。

常见类型

  • 最大池化(Max Pooling):在每个局部区域中选取最大值作为该区域的输出。例如,对于一个 2x2 的局部区域,将其中 4 个元素中的最大值提取出来。最大池化能够保留输入数据中的主要特征,因为最大值往往对应着数据中最显著的特征信息,有助于突出重要的边缘、纹理等特征。
  • 平均池化(Average Pooling):计算每个局部区域内所有元素的平均值作为输出。它会对局部区域的信息进行平均化处理,能保留更多的背景信息,但相对而言可能会模糊一些突出的特征。

作用

  • 降低数据维度:通过减少数据的尺寸,降低后续层的计算量和参数数量,从而加快模型的训练速度,减少过拟合的风险。例如,在处理高分辨率的图像时,池化操作可以显著减小特征图的大小。
  • 增强特征的平移不变性:即使输入数据在局部区域内发生了一定的平移,池化操作提取的特征仍然保持相对稳定。这使得模型在面对物体位置变化时具有更强的鲁棒性。
  • 提取重要特征:最大池化可以突出数据中的重要特征,帮助模型聚焦于更关键的信息,提高模型的特征表达能力。

2.nn.MaxPool1d/2d/3d 最大池化:取局部最大值

nn.MaxPool1dnn.MaxPool2dnn.MaxPool3d 是 PyTorch 中用于实现不同维度最大池化操作的模块。最大池化的核心思想是在输入数据的局部区域内选取最大值作为该区域的输出,以此来减少数据的维度,同时保留重要的特征信息。

nn.MaxPool1d

  • 适用场景:主要用于处理一维数据,如时间序列数据或一维的特征向量。输入数据的形状通常为 (N, C, L),其中 N 是批量大小,C 是通道数,L 是序列长度。

  • 参数

    • kernel_size:池化窗口的大小,是一个整数,表示在序列长度方向上的窗口大小。
    • stride:池化窗口的步长,默认为 kernel_size
    • padding:在输入数据的边界填充 0 的数量,默认为 0。
    1
    2
    3
    4
    5
    6
    # 定义输入数据
    input_data = torch.randn(1, 1, 10) # 批量大小为 1,通道数为 1,序列长度为 10
    # 创建 MaxPool1d 层
    max_pool_1d = nn.MaxPool1d(kernel_size=2, stride=2)
    # 进行最大池化操作
    output = max_pool_1d(input_data)

nn.MaxPool2d

  • 适用场景:常用于处理二维数据,如图像数据。输入数据的形状通常为 (N, C, H, W),其中 N 是批量大小,C 是通道数,H 是高度,W 是宽度。

  • 参数:

    • kernel_size:池化窗口的大小,可以是一个整数或元组。如果是整数,则表示正方形窗口的边长;如果是元组,则分别表示高度和宽度方向的窗口大小。
    • stride:池化窗口的步长,默认为 kernel_size
    • padding:在输入数据的边界填充 0 的数量,默认为 0。
    1
    2
    3
    4
    5
    6
    # 定义输入数据
    input_data = torch.randn(1, 1, 4, 4) # 批量大小为 1,通道数为 1,高度为 4,宽度为 4
    # 创建 MaxPool2d 层
    max_pool_2d = nn.MaxPool2d(kernel_size=2, stride=2)
    # 进行最大池化操作
    output = max_pool_2d(input_data)

nn.MaxPool3d

  • 适用场景:适用于处理三维数据,如 3D 医学图像或视频数据。输入数据的形状通常为 (N, C, D, H, W),其中 N 是批量大小,C 是通道数,D 是深度,H 是高度,W 是宽度。

  • 参数:

    • kernel_size:池化窗口的大小,可以是一个整数或元组。如果是整数,则表示立方体窗口的边长;如果是元组,则分别表示深度、高度和宽度方向的窗口大小。
    • stride:池化窗口的步长,默认为 kernel_size
    • padding:在输入数据的边界填充 0 的数量,默认为 0。
    1
    2
    3
    4
    5
    6
    # 定义输入数据
    input_data = torch.randn(1, 1, 2, 4, 4) # 批量大小为 1,通道数为 1,深度为 2,高度为 4,宽度为 4
    # 创建 MaxPool3d 层
    max_pool_3d = nn.MaxPool3d(kernel_size=2, stride=2)
    # 进行最大池化操作
    output = max_pool_3d(input_data)

3.nn.AvgPool1d/2d/3d 平均池化:取局部平均值

原理同上

4.nn.AdaptiveMaxPool1d/2d/3d 自适应池化:动态调整输出尺寸

nn.AdaptiveMaxPool1dnn.AdaptiveMaxPool2dnn.AdaptiveMaxPool3d 是 PyTorch 中用于实现自适应最大池化操作的模块。与普通的最大池化(如 nn.MaxPool1dnn.MaxPool2dnn.MaxPool3d)不同,自适应最大池化允许用户直接指定输出的尺寸,而不是像普通池化那样指定池化窗口的大小和步长,它会根据指定的输出尺寸动态地调整池化窗口的大小和步长,从而得到期望的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1D:
# 定义输入数据
input_data = torch.randn(1, 1, 10) # 批量大小为 1,通道数为 1,序列长度为 10

# 创建 AdaptiveMaxPool1d 层,指定输出长度为 5
adaptive_max_pool_1d = nn.AdaptiveMaxPool1d(output_size=5)

# 进行自适应最大池化操作
output = adaptive_max_pool_1d(input_data)


2D:
# 定义输入数据
input_data = torch.randn(1, 1, 8, 8) # 批量大小为 1,通道数为 1,高度为 8,宽度为 8

# 创建 AdaptiveMaxPool2d 层,指定输出高度和宽度为 4
adaptive_max_pool_2d = nn.AdaptiveMaxPool2d(output_size=4)

# 进行自适应最大池化操作
output = adaptive_max_pool_2d(input_data)



3D:
# 定义输入数据
input_data = torch.randn(1, 1, 4, 8, 8) # 批量大小为 1,通道数为 1,深度为 4,高度为 8,宽度为 8

# 创建 AdaptiveMaxPool3d 层,指定输出深度、高度和宽度为 2
adaptive_max_pool_3d = nn.AdaptiveMaxPool3d(output_size=2)

# 进行自适应最大池化操作
output = adaptive_max_pool_3d(input_data)

5.nn.FractionalMaxPool2d 分数池化:随机分数步长池化

nn.FractionalMaxPool2d 是 PyTorch 中用于实现分数最大池化(Fractional Max Pooling)的模块。传统的最大池化(如 nn.MaxPool2d)使用固定的步长和池化窗口大小进行下采样,而分数最大池化引入了随机的分数步长,能够在池化过程中提供更多的随机性和灵活性,有助于提高模型的泛化能力。

分数最大池化的核心思想是通过随机选择分数步长来确定池化窗口的位置,从而实现下采样。在每次前向传播时,池化窗口的位置是随机的,但会保证输出的尺寸满足用户指定的要求。具体来说,它会根据输入的尺寸和期望的输出尺寸,随机生成一系列分数步长,然后根据这些步长移动池化窗口并进行最大池化操作。

优点

  • 增强泛化能力:由于分数最大池化引入了随机的分数步长,使得模型在每次训练时看到的池化结果不同,增加了数据的多样性,有助于模型学习到更鲁棒的特征,从而提高模型的泛化能力。
  • 减少过拟合:随机池化的过程可以看作是一种数据增强的方式,能够在一定程度上减少模型对训练数据的过拟合。

缺点

  • 计算复杂度较高:由于需要随机生成分数步长并进行池化操作,分数最大池化的计算复杂度相对较高,可能会增加模型的训练时间。
  • 可解释性较差:随机池化的过程使得池化窗口的位置不固定,增加了模型的不确定性,导致其可解释性相对较差。

❓分数池化和自适应池化的区别

  • 分数池化(nn.FractionalMaxPool2d

引入随机的分数步长来确定池化窗口的位置。在每次前向传播时,会根据输入尺寸和期望的输出尺寸随机生成分数步长,然后依据这些步长移动池化窗口进行最大池化操作。也就是说,每次池化时窗口的位置是随机的,具有一定的不确定性。

适用于对模型泛化能力要求较高、需要缓解过拟合问题的任务,如在图像分类、目标检测等任务中,当训练数据有限或者模型容易过拟合时,可以考虑使用分数池化。

  • 自适应池化(nn.AdaptiveMaxPool2d

直接根据用户指定的输出尺寸来动态调整池化窗口的大小和步长。它会自动计算出合适的池化窗口参数,以确保输出的特征图尺寸符合要求。整个过程是确定性的,对于相同的输入和指定的输出尺寸,每次得到的结果都是相同的。

适用于需要统一不同样本特征尺寸的场景,例如在处理不同分辨率的图像数据时,使用自适应池化可以将不同尺寸的输入图像转换为固定尺寸的特征图,方便后续的处理和模型训练。同时,在一些对计算效率有较高要求的场景中,自适应池化也是一个不错的选择。

Dropout(丢弃) 层
  1. 什么是Dropout(丢弃)?

Dropout 是深度学习中一种常用的正则化技术,主要用于防止神经网络过拟合。

原理

在神经网络训练过程中,神经元之间可能会产生复杂的共适应关系,即某些神经元会依赖其他特定神经元的输出。这可能导致模型对训练数据过度拟合,在新数据上的泛化能力变差。Dropout 通过随机 “丢弃”(暂时忽略)一部分神经元及其连接,打破这种共适应关系,使模型更加鲁棒。

工作机制

  • 训练阶段:在每次训练迭代中,对于每个神经元,以一定的概率 (称为 Dropout 率)将其暂时从网络中丢弃,即该神经元在本次前向传播和反向传播中不参与计算。通常,输入层的 Dropout 率较低(如 0.2),隐藏层的 Dropout 率较高(如 0.5)。
  • 测试阶段:在测试或推理时,所有神经元都参与计算,但为了平衡训练和测试阶段神经元的输出规模,需要将每个神经元的输出乘以$(1-p)$。不过,在实际实现中,也可以采用倒置 Dropout(Inverted Dropout)技术,在训练时就对保留的神经元输出除以$(1-p)$,这样测试时就无需额外操作。

作用

  • 减少过拟合:Dropout 相当于在每次迭代中训练一个不同的子网络,这些子网络共享参数。通过这种方式,模型不会过度依赖于任何一个神经元或一组神经元,从而减少对训练数据的过拟合,提高在新数据上的泛化能力。
  • 集成学习效果:从某种意义上说,Dropout 可以看作是一种集成学习方法,因为每次训练的子网络都可以看作是一个独立的模型,最终的模型相当于这些子网络的集成。

缺点

  • 训练时间增加:由于每次迭代中部分神经元被丢弃,模型需要更多的迭代次数才能收敛,因此会增加训练时间。
  • 超参数选择困难:Dropout 率 是一个超参数,需要通过实验进行调整。如果$p$设置过大,模型可能欠拟合;如果$p$设置过小,则可能无法有效防止过拟合。
  1. nn.Dropout 标准Dropout: 随机置零神经元

nn.Dropout 是 PyTorch 中用于实现标准 Dropout 操作的模块。Dropout 是一种在训练神经网络时常用的正则化技术,其核心思想是在每次训练迭代中,以一定的概率 随机 “丢弃”(将输出置为 0)网络中的部分神经元,使得模型不会过度依赖于某些特定的神经元,从而减少过拟合的风险,提高模型的泛化能力。

应用场景

  • 全连接层之后:在多层感知机(MLP)中,通常在全连接层之后添加 Dropout 层,以防止过拟合。
  • 卷积神经网络(CNN):在一些 CNN 架构中,也会在全连接层之前或之后使用 Dropout 层。不过,在卷积层中使用 Dropout 的情况相对较少,因为卷积层本身具有一定的平移不变性和稀疏性。

优点缺点同上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch
import torch.nn as nn

# 创建输入张量
input_tensor = torch.randn(10)

# 创建 Dropout 层,设置丢弃概率为 0.2
dropout = nn.Dropout(p=0.2)

# 开启训练模式
dropout.train()
output_train = dropout(input_tensor)

# 开启评估模式
dropout.eval()
output_eval = dropout(input_tensor)

print("输入张量:", input_tensor)
print("训练模式下的输出:", output_train)
print("评估模式下的输出:", output_eval)

代码解释

  • 输入张量input_tensor 是一个包含 10 个随机数的一维张量。
  • Dropout 层实例:使用 nn.Dropout(p = 0.2) 创建一个 Dropout 层实例,其中 p 表示每个神经元被丢弃的概率,这里设置为 0.2。
  • 训练模式:调用 dropout.train() 将 Dropout 层设置为训练模式。在训练模式下,Dropout 会按照设定的概率随机将部分神经元的输出置为 0,并对保留的神经元输出进行缩放(采用倒置 Dropout 方式,除以 (1 - p))。
  • 评估模式:调用 dropout.eval() 将 Dropout 层设置为评估模式。在评估模式下,Dropout 层不进行任何丢弃操作,直接返回输入张量,因为在测试阶段不需要使用 Dropout 来防止过拟合。

❓为什么平移不变性和稀疏性不容易出现过拟合,进而不需要Dropout

平移不变性是指当输入数据发生平移时,模型的输出结果保持不变。在卷积神经网络(CNN)中,卷积层通过共享卷积核的方式自然地具备了一定程度的平移不变性。例如,在图像识别任务中,无论目标物体在图像中的位置如何移动,卷积核都能检测到相同的特征。

具有平移不变性的模型更关注数据中的本质特征,而不是特征出现的具体位置。这使得模型学到的特征具有更强的泛化能力,能够适应不同位置的相同特征,扩大了训练数据的多样性。

模型在这种多样化的数据上学习,能够更好地捕捉数据的整体分布,而不是过度拟合训练数据中的特定样本。

稀疏性是指数据或模型中的大部分元素为零或接近零。在深度学习中,稀疏性可以体现在数据本身(如稀疏矩阵)或模型的参数(如稀疏连接的神经网络)上。例如,在自然语言处理中,词嵌入向量通常是稀疏的,因为一个句子中只包含少量的词汇。

稀疏性意味着模型只关注数据中的少数重要特征,而忽略了大量的无关信息。稀疏数据中的非零元素往往代表了数据中的重要特征,因此模型能够更准确地捕捉数据的本质规律。这种对重要特征的聚焦使得模型在新数据上也能有较好的表现,减少了过拟合的可能性。

Dropout 主要用于防止模型过拟合,通过随机丢弃部分神经元来减少神经元之间的共适应关系。而具有平移不变性和稀疏性的模型本身已经具备了一定的抗过拟合能力,因此在这些情况下,可能不需要额外使用 Dropout 来防止过拟合。但这并不意味着在所有具有平移不变性和稀疏性的模型中都绝对不需要 Dropout,具体情况还需要根据模型的复杂度、训练数据的规模等因素来综合考虑。

  1. nn.Dropout1d/2d/3d 空间Dropout:按通道/空间置零

标准的 nn.Dropout 是对输入张量中的每个元素独立地以一定概率进行置零操作。而空间 Dropout 则是按通道或空间维度进行置零,即一次会将整个通道或空间区域的元素置零。这样做的好处是可以更好地保留特征之间的相关性,尤其适用于处理具有空间结构的数据,如序列数据(nn.Dropout1d)、图像数据(nn.Dropout2d)和 3D 数据(nn.Dropout3d)。

嵌入层
  1. 什么是嵌入?

嵌入层(Embedding Layer)是深度学习中一种特殊的神经网络层,主要用于将离散的类别数据(如单词、商品 ID、用户 ID 等)转换为连续的向量表示。

在很多实际问题中,数据是以离散的类别形式存在的,例如在自然语言处理里的单词,每个单词就是一个离散的类别。然而,大多数机器学习和深度学习算法更适合处理连续的数值数据。嵌入层的作用就是搭建起离散类别数据和连续向量空间之间的桥梁,把每个离散类别映射为一个固定长度的向量。

工作原理

  • 构建查找表:嵌入层本质上是一个可学习的查找表(矩阵)。假设一共有$V$个不同的离散类别,每个类别要被映射成维度为$d$的向量,那么这个查找表就是一个形状为$V \times d$的矩阵。矩阵的每一行对应一个离散类别的向量表示。
  • 索引查找:当输入一个离散类别的索引时,嵌入层会根据这个索引从查找表中取出对应的行向量作为输出。在训练过程中,这个查找表的参数(即每个向量的元素值)会通过反向传播算法不断更新,从而让每个向量能够更好地表示其对应的离散类别。

作用

  • 捕捉语义信息:通过学习得到的嵌入向量能够捕捉离散类别之间的语义关系。例如在词嵌入中,意思相近的单词对应的向量在向量空间中会比较接近。像 “苹果” 和 “香蕉” 的向量可能距离较近,因为它们都属于水果类别。
  • 降低维度:相比于使用独热编码(One - Hot Encoding)来表示离散类别,嵌入向量的维度通常要低得多。独热编码会产生一个非常高维且稀疏的向量,而嵌入向量是低维且密集的,这有助于减少计算量和内存占用,同时也能提高模型的训练效率和泛化能力。

应用场景

  • 自然语言处理:在各种自然语言处理任务中,如文本分类、情感分析、机器翻译、命名实体识别等,词嵌入层是必不可少的组件。它能将文本中的单词转换为向量,让模型可以对文本进行有效的处理和分析。
  • 推荐系统:可以将用户 ID 和商品 ID 分别映射为用户嵌入向量和商品嵌入向量,通过计算这些向量之间的相似度,为用户推荐合适的商品。
  • 知识图谱:把图谱中的实体和关系映射为向量,有助于进行知识推理、实体分类等任务。
  1. nn.Embedding 词嵌入:nn.Embedding

  2. nn.Embedding 稀疏嵌入:高效处理变长序列

稀疏层
  1. 什么是稀疏?

  2. nn.Linear(输入为稀疏张量) 稀疏全连接:稀疏矩阵乘法

视觉专用层
  1. nn.PixelShuffle 像素重排:子像素卷积(超分辨率)

  2. nn.Unfold 像素展开:滑动窗口提取局部块

  3. nn.Fold 像素折叠:逆操作于 Unfold

模型容器
  1. 什么是容器?

  2. nn.Sequential 层字典

  3. nn.ModuleList 动态层列表

  4. nn.ModuleDict层字典

优化器和损失函数

优化器(Optimizers)
什么是优化器?
经典优化器
  1. torch.optim.SGD(含动量)

  2. torch.optim.Adam, AdamW, RMSprop

学习率调度
  1. 什么是学习率调度?

  2. 相关优化器lr_scheduler.StepLR, CosineAnnealingLR, OneCycleLR

梯度裁剪
  1. 什么是梯度裁剪?

  2. nn.utils.clip_grad_norm_

损失函数
什么是损失函数?
分类任务

nn.CrossEntropyLossnn.BCEWithLogitsLoss

回归任务

nn.MSELoss, nn.L1Loss, nn.HuberLoss

生成任务
  1. 生成什么?

  2. 相关损失函数nn.BCELoss, nn.KLDivLoss, 对抗损失(如 WGAN-GP)

预训练模型(torchvision.models)与迁移学习

计算机视觉模型
经典 CNN 架构(通过 torchvision.models

❓什么是CNN

  1. ResNet

  2. VGG

  3. EfficientNet

Transformer 模型
  1. Vision Transformer (ViT)

  2. Swin Transformer

自然语言处理模型
预训练语言模型(通过 transformers 库)
  1. BERT

  2. GPT

迁移学习策略

什么是迁移学习策略

特征提取(冻结部分层)
微调(Fine-tuning)
使用预训练特征(如 CLIP)

数据管道 (Data Pipeline)

数据集类 Dataset

自定义数据集实现

数据加载器 DataLoader
  1. 批量加载

  2. 多进程加速 (num_workers)

数据增强``torchvision.transforms`

模型部署与性能优化

TorchScript 模型导出
ONNX 格式转换
混合精度训练 (torch.cuda.amp)

OS操作系统接口模块

Numpy

np.stack