神经网络的数学构建块
PyTorch 中神经网络的数学基础
Section titled “PyTorch 中神经网络的数学基础”数学是机器学习和神经网络的基石。理解核心数学概念有助于更好地设计模型、理解算法行为以及有效地排除故障。
核心数学概念如线性代数(向量、矩阵、张量/Tensor)、微积分(用于通过反向传播期间计算的梯度进行优化)以及概率与统计(用于建模不确定性、定义损失函数和评估模型),是理解和开发神经网络的基础。
在 PyTorch 中,所有这些数学实体以及数据的主要数据结构是 Tensor(张量)。让我们探索 Tensor 如何表示各种数学对象以及如何对它们进行操作。
Tensor:PyTorch 的核心
Section titled “Tensor:PyTorch 的核心”PyTorch Tensor 是一个多维数组,概念上类似于 NumPy 的 ndarray。Tensor 对 PyTorch 至关重要,因为它们可以高效地存储和操作数值数据,并且还具备在 GPU 上运行的能力,以实现大规模并行计算,这对于深度学习至关重要。
Tensor 的主要属性包括:
shape:一个torch.Size对象(一个元组),描述 Tensor 的维度(例如,对于 3x4 矩阵是(3, 4))。dtype:元素的数据类型(例如,torch.float32用于单精度浮点数,torch.int64用于 64 位整数)。device:存储 Tensor 数据的设备(例如,cpu或特定的 GPU,如cuda:0)。ndim:Tensor 的维数(rank)。
创建 Tensor:
import torchimport numpy as np
# 1. 从 Python 列表或序列使用 torch.tensor()data = [[1, 2], [3, 4]]x_from_list = torch.tensor(data, dtype=torch.float32) # 显式设置数据类型print(f'从列表中创建的 Tensor:\n{x_from_list}')print(f'形状: {x_from_list.shape}, 数据类型: {x_from_list.dtype}, 设备: {x_from_list.device}')
# 2. 从 NumPy 数组使用 torch.from_numpy()# (如果在 CPU 上,与 NumPy 数组共享内存;对 NumPy 数组的更改会反映在 Tensor 中,反之亦然)numpy_array = np.array(data, dtype=np.float32)x_from_numpy = torch.from_numpy(numpy_array)print(f'\n从 NumPy 数组创建的 Tensor:\n{x_from_numpy}')
# 3. 创建具有特定值或属性的 Tensorx_ones = torch.ones_like(x_from_list) # 创建一个与 x_from_list 具有相同形状/数据类型,且所有元素为 1 的 Tensorprint(f'\nOnes Tensor (与 x_from_list 类似):\n{x_ones}')
x_rand = torch.rand_like(x_from_list) # 创建一个与 x_from_list 具有相同形状,且元素为 [0,1) 范围内的随机值的 Tensorprint(f'\n随机 Tensor (与 x_from_list 类似):\n{x_rand}')
# 4. 创建具有特定形状的 Tensorshape_tuple = (2, 3, 4)rand_tensor_shape = torch.rand(shape_tuple) # 随机值 [0,1)ones_tensor_shape = torch.ones(shape_tuple, dtype=torch.int)zeros_tensor_shape = torch.zeros(shape_tuple)
print(f'\n形状为 {shape_tuple} 的随机 Tensor:\n{rand_tensor_shape}')标量 (0D Tensor)
Section titled “标量 (0D Tensor)”标量(Scalar)是一个单一的数字,在 PyTorch 中表示为零维 Tensor(没有轴的 Tensor)。损失函数计算出的损失值或某些指标通常是标量 Tensor。
import torch
scalar_val = torch.tensor(3.14159)print(f'标量值: {scalar_val}')print(f'形状: {scalar_val.shape}, 维数 (ndim): {scalar_val.ndim}, 数据类型: {scalar_val.dtype}')
# 从单元素 Tensor(标量)获取 Python 数值:python_number = scalar_val.item()print(f'从 scalar_val 获取的 Python 数值: {python_number}')当你需要实际的 Python 数值时,使用 .item() 至关重要,例如用于打印、日志记录或在 PyTorch 计算图之外进行条件逻辑判断。尝试对一个包含多个元素的 Tensor 使用 .item() 将导致错误。
向量 (1D Tensor)
Section titled “向量 (1D Tensor)”向量(Vector)是一个有序的数字数组,表示为一维 Tensor(一个轴)。在机器学习中,向量通常表示单个数据样本的特征、神经网络层中的偏置(biases),或者词语/物品的嵌入(embeddings)。
import torch
vector_data = torch.tensor([1.0, 2.5, -3.0, 4.2])print(f'向量: {vector_data}')print(f'形状: {vector_data.shape}, 维数 (ndim): {vector_data.ndim}')
# 使用 torch.arange() 创建序列向量sequence_vec = torch.arange(start=0, end=5, step=1, dtype=torch.float32) # 创建 Tensor([0., 1., 2., 3., 4.])print(f'序列向量 (来自 arange): {sequence_vec}, 形状: {sequence_vec.shape}')向量的 shape 将是 torch.Size([N]),其中 N 是元素的数量。其 ndim 将为 1。
矩阵 (2D Tensor)
Section titled “矩阵 (2D Tensor)”矩阵(Matrix)是一个二维数字数组(组织成行和列),表示为二维 Tensor。矩阵是线性代数的基础,用于表示表格型数据集、一批一维特征向量、图像数据(例如灰度图像),或神经网络层中的权重参数(例如在全连接层中)。
import torchimport numpy as np
matrix_data = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)print(f'矩阵:\n{matrix_data}')print(f'形状: {matrix_data.shape}, 维数 (ndim): {matrix_data.ndim}')
# 示例:使用 Boston Housing 数据集特征表示一个小批量数据。# 注意:Boston housing 数据集存在伦理问题,并在 scikit-learn >= 1.2 版本中标记为弃用。# 对于此教学示例,我们演示如何加载它,但建议在新项目中使用替代数据集。# 如果 scikit-learn 的 load_boston 不可用,将使用虚拟数据。try: from sklearn.datasets import load_boston boston = load_boston() boston_data_np = boston.data # NumPy 数组,形状为 (506, 13)except ImportError: print("此 scikit-learn 版本中 sklearn.datasets.load_boston 不可用。将使用虚拟数据进行 Boston housing 示例。") boston_data_np = np.random.rand(506, 13) # (样本数, 特征数)
boston_tensor = torch.from_numpy(boston_data_np).float() # 转换为 float32 Tensorprint(f'\nBoston 数据 Tensor 形状: {boston_tensor.shape}') # 预期: torch.Size([506, 13])
# 为简洁起见,显示一小部分(前 2 个样本,前 4 个特征)print('Boston Tensor 的前 2 个样本,前 4 个特征:')print(boston_tensor[:2, :4])矩阵的 shape 将是 torch.Size([行数, 列数])。其 ndim 将为 2。
高维 Tensor (nD Tensor)
Section titled “高维 Tensor (nD Tensor)”PyTorch 无缝支持维数多于两维的 Tensor。这对于许多现代深度学习应用至关重要:
- 3D Tensor:可以表示一批序列(例如,在自然语言处理中:
(batch_size, sequence_length, feature_dim))、时间序列数据或体积数据(例如 MRI 扫描)。 - 4D Tensor:通常用于一批彩色图像,遵循
(batch_size, channels, height, width)(PyTorch NCHW 约定)或(batch_size, height, width, channels)(NHWC,在 PyTorch 核心中较少见,但某些库/框架使用)等约定。 - 5D Tensor:可以表示一批视频数据(例如,
(batch_size, frames, channels, height, width))。
import torch
# 示例:一个 4D Tensor,表示一批 16 个 RGB 图像,每个图像为 224x224 像素# (N: 批量大小, C: 通道数, H: 高度, W: 宽度)batch_of_images = torch.rand(16, 3, 224, 224)print(f'批量图像 Tensor 形状: {batch_of_images.shape}, 维数: {batch_of_images.ndim}')
# 示例:一个 3D Tensor,表示一批 10 个序列,每个序列长度为 20,每一步有 5 个特征batch_of_sequences = torch.rand(10, 20, 5)print(f'批量序列 Tensor 形状: {batch_of_sequences.shape}, 维数: {batch_of_sequences.ndim}')重要的 Tensor 操作
Section titled “重要的 Tensor 操作”PyTorch 提供了一个全面的 Tensor 操作库,其中许多操作与 NumPy 的 API 相似,方便 NumPy 用户轻松过渡。大多数操作既可以作为 torch 模块中的函数(例如 torch.add(a, b)),也可以作为 Tensor 对象的方法(例如 a.add(b))使用。一些关键操作包括:
-
逐元素操作(Element-wise Operations):数学操作,如加法(
+或torch.add)、减法(-)、乘法(*或torch.mul)、除法(/)、指数(torch.exp)、对数(torch.log)等,逐元素应用。 -
矩阵操作(Matrix Operations):线性代数操作,如矩阵乘法(
torch.matmul()或@运算符)、点积(torch.dot)、转置(tensor.T或torch.transpose())、矩阵求逆(torch.inverse())、奇异值分解(torch.svd())。 -
归约操作(Reduction Operations):减少 Tensor 维数的操作,如
torch.sum()、torch.mean()、torch.max()、torch.min()、torch.std()。这些操作可以作用于所有元素或沿特定维度(使用dim参数)。 -
索引、切片、合并、修改(Indexing, Slicing, Joining, Mutating):PyTorch 提供了类似于 NumPy 的丰富的索引和切片功能。Tensor 可以被拼接(
torch.cat)、堆叠(torch.stack)、分割(torch.split),并可以进行原地修改(例如tensor.add_()- 注意下划线表示原地操作)。 -
形状重塑操作(Reshaping Operations):
tensor.reshape()或tensor.view()(如果可能,创建共享底层数据的新 Tensor 视图)。tensor.squeeze()移除大小为 1 的维度,而tensor.unsqueeze()添加一个大小为 1 的维度。 -
广播(Broadcasting):与 NumPy 类似,PyTorch 支持广播,在逐元素操作中,只要 Tensor 的形状根据广播规则兼容,广播就会自动扩展较小的 Tensor 以匹配较大 Tensor 的形状。这使得代码更简洁,无需显式平铺数组。
import torch
a = torch.tensor([[1., 2.], [3., 4.]])b = torch.tensor([[5., 6.], [7., 8.]])
# 逐元素加法sum_ab = a + bprint(f'求和 (a + b):\n{sum_ab}')
# 矩阵乘法prod_ab = torch.matmul(a, b) # 或 a @ bprint(f'\n矩阵乘积 (a @ b):\n{prod_ab}')
# 广播示例:将一个向量加到矩阵的每一行c_vec = torch.tensor([10., 20.]) # 形状 (2,)a_matrix = torch.tensor([[1., 2.], [3., 4.], [5., 6.]]) # 形状 (3, 2)broadcast_sum = a_matrix + c_vec # c_vec 广播到形状 (3, 2)print(f'\n广播示例 (a_matrix + c_vec):\n{broadcast_sum}')
# 形状重塑reshaped_a = a.reshape(4, 1)print(f'\n重塑后的 a (从 2x2 到 4x1):\n{reshaped_a}')
# 索引print(f'\na 中 (0,1) 位置的元素: {a[0, 1]}') # 返回一个标量 Tensorprint(f'a 的第一行: {a[0, :]}') # 返回一个一维 TensorTensor 在神经网络中的作用
Section titled “Tensor 在神经网络中的作用”Tensor 是在 PyTorch 中实现的神经网络中基本的数据载体:
-
输入数据和标签:图像、文本序列、音频信号或其他特征的批次被预处理并作为 Tensor 输入到网络。标签或目标值也是 Tensor。
-
模型参数(权重和偏置):神经网络层的可学习参数(例如线性层或卷积核的权重和偏置)存储为 Tensor。这些 Tensor 通常设置了
requires_grad=True,表示 PyTorch 的 autograd 引擎应该跟踪对它们的操作,以便在反向传播期间计算梯度。 -
中间激活值:每一层应用其操作和激活函数后的输出都是 Tensor。这些 Tensor 从输入流经网络到输出。
-
梯度:在训练阶段(反向传播)期间,计算损失函数相对于模型参数(以及其他设置了
requires_grad=True的 Tensor)的梯度。这些梯度本身也是 Tensor,其形状与对应的参数相同。 -
损失值:通过使用损失函数(例如均方误差、交叉熵损失)比较模型的预测结果与真实目标值计算出的最终损失,通常是一个标量 Tensor。
常见陷阱和最佳实践
Section titled “常见陷阱和最佳实践”-
数据类型(
dtype):确保参与操作的 Tensor 具有兼容的数据类型。例如,torch.float32Tensor 和torch.int64Tensor 之间的操作可能导致错误或意外的类型提升。使用tensor.to(desired_dtype)或tensor.type(desired_dtype_str)进行显式类型转换(例如,tensor.float()是tensor.to(torch.float32)的快捷方式)。 -
Tensor 形状:Tensor 形状不匹配是运行时错误的常见来源,尤其是在矩阵乘法或数据在层之间传递时。始终注意预期和实际的 Tensor 形状。在调试时,大量使用
print(tensor.shape)。 -
.item()用于标量:当你有一个单元素 Tensor(例如损失值)并且需要它的 Python 数值时(例如用于打印、日志记录或标准 Python 条件语句),使用scalar_tensor.item()。避免在 Python 条件句中使用 Tensor 本身,如if loss_tensor < 0.1:,因为这可能含义不清或导致错误。 -
CPU vs. GPU(
device):参与操作的 Tensor 必须位于同一设备上(例如,全部在 CPU 上,或全部在同一 GPU 上)。尝试对不同设备上的 Tensor 进行操作将导致错误。使用tensor.to(device_object)移动 Tensor(例如,device = torch.device('cuda' if torch.cuda.is_available() else 'cpu'); tensor_on_gpu = tensor_on_cpu.to(device))。模型也应该使用model.to(device)移动到所需的设备上。 -
原地操作(In-place Operations):带有后缀下划线的操作(例如
tensor.add_()、tensor.relu_())会原地修改 Tensor。虽然它们可以节省内存,但如果 Tensor 的原始状态在其他地方用于梯度计算,它们可能会给 autograd 带来问题。谨慎使用它们,尤其是在神经网络模块内部。
牢固掌握 Tensor 的创建、属性和操作,对于有效使用 PyTorch 进行任何机器学习或深度学习任务都是绝对关键的。请参阅 PyTorch 官方文档,获取 Tensor 操作及其使用的全面列表。