机器学习算法原理与实践-入门(七):深度学习框架PyTorch的Tensor

机器学习算法原理与实践-入门(七):深度学习框架PyTorch的Tensor

在前几篇文章中,我们从基础机器学习算法逐步深入到深度学习领域。今天,我们将正式进入现代深度学习的世界,学习PyTorch框架的核心数据结构------Tensor。这是理解和使用所有深度学习框架的基础,也是从传统机器学习向深度学习过渡的关键一步。


一、PyTorch:现代深度学习的标准工具

1.1 什么是PyTorch?

PyTorch是由Meta公司(原Facebook)人工智能研究团队开发和维护的开源深度学习框架。它已经成为学术界和工业界最受欢迎的深度学习框架之一。

1.2 PyTorch的核心特点

  1. 动态计算图:计算图在运行时构建,更加灵活直观
  2. 自动求导:内置自动微分系统,简化梯度计算
  3. GPU加速:无缝支持GPU计算,大幅提升训练速度
  4. 丰富的生态系统:提供完整的神经网络库和工具集

1.3 为什么选择PyTorch?

  • 易用性:Python风格的API,学习曲线平缓
  • 灵活性:动态图机制适合研究和实验
  • 社区支持:活跃的社区和丰富的文档
  • 生产就绪:支持将模型部署到生产环境

二、Tensor:深度学习的基本数据结构

2.1 什么是Tensor?

Tensor是PyTorch中最基本的数据结构,可以看作是多维数组。它在概念上类似于NumPy的ndarray,但具有额外的功能:

  • GPU支持:可以在GPU上运行加速计算
  • 自动求导:自动计算梯度,支持反向传播
  • 动态计算:支持动态构建计算图

2.2 Tensor的维度

Tensor可以有不同维度,对应不同的数据结构:

维度 名称 示例 说明
0维 标量 tensor(3.14) 单个数值
1维 向量 tensor([1, 2, 3]) 一维数组
2维 矩阵 tensor([[1, 2], [3, 4]]) 二维数组
3维及以上 高阶Tensor tensor([[[1, 2]]]) 三维及更高维数组

2.3 Tensor与NumPy数组的对比

虽然Tensor和NumPy数组很相似,但有几个关键区别:

特性 PyTorch Tensor NumPy ndarray
设备支持 CPU和GPU 仅CPU
自动求导 支持 不支持
计算图 动态计算图 无计算图
数据类型 丰富的类型系统 类似但不同

三、Tensor的存储机制:Storage与Metadata

3.1 两部分组成的Tensor

每个Tensor都由两部分组成:

  1. Storage(存储):实际数据的一维连续内存块
  2. Metadata(元数据):描述Tensor结构和属性的信息

3.2 元数据的内容

元数据包含以下关键信息:

  • 形状(shape):Tensor各维度的大小
  • 数据类型(dtype):Tensor中元素的数据类型
  • 步长(stride):在每个维度上移动时在存储中跨越的元素数
  • 存储偏移量(storage_offset):从存储开始处的偏移
  • 设备(device):Tensor所在的设备(CPU或GPU)

3.3 共享存储机制

多个Tensor可以共享同一个存储,即使它们的形状和步长不同。这种机制节省内存并提高效率:

python 复制代码
a = torch.arange(6).reshape(2, 3)  # 原始Tensor
b = a.transpose(0, 1)              # 转置Tensor,共享存储

四、Tensor的数据类型

4.1 支持的数据类型

PyTorch支持多种数据类型,主要分为三类:

整数类型
  • torch.int8:8位有符号整数(-128 到 127)
  • torch.int16:16位有符号整数(-32768 到 32767)
  • torch.int32:32位有符号整数(-2147483648 到 2147483647)
  • torch.int64:64位有符号整数
无符号整数类型
  • torch.uint8:8位无符号整数(0 到 255)
  • torch.uint16:16位无符号整数(0 到 65535)
  • torch.uint32:32位无符号整数
  • torch.uint64:64位无符号整数
浮点数类型
  • torch.float16:16位浮点数(半精度)
  • torch.float32:32位浮点数(单精度)
  • torch.float64:64位浮点数(双精度)

4.2 浮点数的IEEE 754标准

浮点数在计算机中按照IEEE 754标准存储:

  • float16(半精度)

    • 符号位:1位
    • 指数部分:5位
    • 小数部分:10位
    • 示例:0.75 → 二进制:0011101000000000
  • float32(单精度)

    • 符号位:1位
    • 指数部分:8位
    • 小数部分:23位
  • float64(双精度)

    • 符号位:1位
    • 指数部分:11位
    • 小数部分:52位

4.3 数据类型的选择原则

  1. 精度需求:根据计算精度选择float16/32/64
  2. 内存限制:内存紧张时使用较小的数据类型
  3. 硬件支持:某些硬件对特定类型有优化
  4. 计算速度:较小类型通常计算更快

五、Tensor的连续性

5.1 连续Tensor的特点

连续Tensor的元素在内存中按照其在Tensor中的顺序紧密存储:

  • 内存访问效率高
  • 支持view()等操作
  • 计算性能优化

5.2 不连续Tensor的产生

某些操作会导致Tensor不连续,例如:

  • 转置(transpose):交换维度
  • 切片(slice):部分选取
  • 跨步索引(strided indexing):非连续索引

5.3 连续性的判断与转换

PyTorch提供了判断和转换Tensor连续性的方法:

  • is_contiguous():判断Tensor是否连续
  • contiguous():将不连续Tensor转换为连续Tensor

转换过程会创建新的内存空间复制数据,因此有内存和时间开销。


六、代码:深入理解Tensor

python 复制代码
import torch

# ============ 1. 创建不同类型和维度的Tensor ============
print("=== 1. 创建不同类型和维度的Tensor ===")

# 创建标量(0维Tensor)
scalar_tensor = torch.tensor(3.14)
print(f"标量Tensor: {scalar_tensor}")
print(f"维度: {scalar_tensor.dim()}")
print(f"形状: {scalar_tensor.shape}")
print()

# 创建向量(1维Tensor)
vector_tensor = torch.tensor([1, 2, 3, 4, 5, 6])
print(f"向量Tensor: {vector_tensor}")
print(f"维度: {vector_tensor.dim()}")
print(f"形状: {vector_tensor.shape}")
print()

# 创建矩阵(2维Tensor)
matrix_tensor = torch.tensor([[1, 2], [3, 4]])
print(f"矩阵Tensor: {matrix_tensor}")
print(f"维度: {matrix_tensor.dim()}")
print(f"形状: {matrix_tensor.shape}")
print()

# ============ 2. 查看Tensor的存储信息 ============
print("=== 2. 查看Tensor的存储信息 ===")

# 创建一个2x3的Tensor
tensor1 = torch.tensor([[1., 2, 3], [4, 5, 6]], dtype=torch.float32)
print(f"Tensor内容:\n{tensor1}")
print(f"形状: {tensor1.shape}")
print(f"数据类型: {tensor1.dtype}")
print(f"设备: {tensor1.device}")
print(f"存储地址: {tensor1.storage().data_ptr()}")
print(f"存储内容: {tensor1.storage().tolist()}")
print()

# ============ 3. 理解共享存储机制 ============
print("=== 3. 理解共享存储机制 ===")

# 创建原始Tensor
a = torch.arange(12).reshape(3, 4)
print(f"原始Tensor a:\n{a}")
print(f"a的存储地址: {a.storage().data_ptr()}")

# 创建转置Tensor(共享存储)
b = a.transpose(0, 1)
print(f"\n转置Tensor b:\n{b}")
print(f"b的存储地址: {b.storage().data_ptr()}")
print(f"a和b是否共享存储: {a.storage().data_ptr() == b.storage().data_ptr()}")
print()

# ============ 4. 理解步长(Stride) ============
print("=== 4. 理解步长(Stride) ===")

# 查看原始Tensor的步长
print(f"a的形状: {a.shape}")
print(f"a的步长: {a.stride()}")

# 步长的解释:
# stride(0) = 4: 在第0维(行)移动一个元素,在存储中需要跨越4个元素
# stride(1) = 1: 在第1维(列)移动一个元素,在存储中需要跨越1个元素

# 查看转置Tensor的步长
print(f"\nb的形状: {b.shape}")
print(f"b的步长: {b.stride()}")
# stride(0) = 1: 在转置后的第0维移动一个元素,在存储中需要跨越1个元素
# stride(1) = 4: 在转置后的第1维移动一个元素,在存储中需要跨越4个元素
print()

# ============ 5. 理解Tensor的连续性 ============
print("=== 5. 理解Tensor的连续性 ===")

# 检查连续性
print(f"a是否连续: {a.is_contiguous()}")
print(f"b是否连续: {b.is_contiguous()}")

# 尝试对不连续Tensor进行view操作
try:
    b_view = b.view(2, 6)
    print(f"b.view(2, 6)成功: \n{b_view}")
except Exception as e:
    print(f"b.view(2, 6)失败: {e}")

# 将不连续Tensor转换为连续Tensor
b_contiguous = b.contiguous()
print(f"\nb转换为连续后: {b_contiguous.is_contiguous()}")

# 现在可以进行view操作
b_contiguous_view = b_contiguous.view(2, 6)
print(f"连续化后的b.view(2, 6): \n{b_contiguous_view}")
print()

# ============ 6. 数据类型转换 ============
print("=== 6. 数据类型转换 ===")

# 创建float32类型的Tensor
float_tensor = torch.tensor([1.5, 2.5, 3.5], dtype=torch.float32)
print(f"原始Tensor (float32): {float_tensor}, 数据类型: {float_tensor.dtype}")

# 转换为float16
float16_tensor = float_tensor.to(torch.float16)
print(f"转换为float16: {float16_tensor}, 数据类型: {float16_tensor.dtype}")

# 转换为int
int_tensor = float_tensor.to(torch.int32)
print(f"转换为int32: {int_tensor}, 数据类型: {int_tensor.dtype}")
print()

# ============ 7. 设备转移 ============
print("=== 7. 设备转移 ===")

# 检查是否有可用的GPU
if torch.cuda.is_available():
    print("CUDA可用,将Tensor转移到GPU")
    gpu_tensor = a.to('cuda')
    print(f"GPU Tensor设备: {gpu_tensor.device}")
    
    # 转移回CPU
    cpu_tensor = gpu_tensor.to('cpu')
    print(f"转移回CPU的设备: {cpu_tensor.device}")
else:
    print("CUDA不可用,使用CPU")

print()

# ============ 8. Tensor的基本运算 ============
print("=== 8. Tensor的基本运算 ===")

# 创建两个Tensor进行运算
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
y = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)

print(f"x:\n{x}")
print(f"y:\n{y}")

# 加法
add_result = x + y
print(f"\n加法 (x + y):\n{add_result}")

# 矩阵乘法
matmul_result = torch.matmul(x, y)
print(f"\n矩阵乘法 (x @ y):\n{matmul_result}")

# 逐元素乘法
mul_result = x * y
print(f"\n逐元素乘法 (x * y):\n{mul_result}")

# 求和
sum_result = x.sum()
print(f"\n所有元素求和: {sum_result}")

# 按维度求和
sum_dim0 = x.sum(dim=0)  # 按第0维(行)求和
sum_dim1 = x.sum(dim=1)  # 按第1维(列)求和
print(f"按第0维求和(列和): {sum_dim0}")
print(f"按第1维求和(行和): {sum_dim1}")

代码关键点解析

1. Tensor的创建与基本属性

python 复制代码
# 创建Tensor
tensor1 = torch.tensor([[1., 2, 3], [4, 5, 6]])

# 查看属性
print(tensor1.shape)    # 形状
print(tensor1.dtype)    # 数据类型
print(tensor1.device)   # 设备
print(tensor1.stride()) # 步长

这些基本属性是理解和使用Tensor的基础。

2. 共享存储机制

python 复制代码
a = torch.arange(12).reshape(3, 4)
b = a.transpose(0, 1)

print(a.storage().data_ptr() == b.storage().data_ptr())  # True

ab共享相同的存储,这意味着修改一个Tensor会影响另一个,同时也节省了内存。

3. 步长的意义

python 复制代码
a = torch.arange(12).reshape(3, 4)
print(a.stride())  # (4, 1)

步长表示在每个维度上移动一个位置时,在底层存储中需要跳过的元素数量。对于形状为(3,4)的矩阵:

  • 在行方向移动:跳过4个元素
  • 在列方向移动:跳过1个元素

4. 连续性的重要性

python 复制代码
b = a.transpose(0, 1)
print(b.is_contiguous())  # False

# 不连续Tensor不能直接view
b_contiguous = b.contiguous()  # 转换为连续
b_view = b_contiguous.view(2, 6)  # 现在可以view

连续性影响内存访问效率和某些操作的支持。contiguous()方法可以创建连续的副本。

5. 设备转移

python 复制代码
if torch.cuda.is_available():
    gpu_tensor = tensor.to('cuda')  # 转移到GPU
    cpu_tensor = gpu_tensor.to('cpu')  # 转移回CPU

PyTorch支持在CPU和GPU之间无缝转移Tensor,这是深度学习加速的关键。

6. 自动求导的基础

虽然本文未涉及自动求导的具体实现,但Tensor的自动求导功能是PyTorch的核心特性:

python 复制代码
x = torch.tensor([1.0], requires_grad=True)  # 启用梯度追踪
y = x ** 2
y.backward()  # 自动计算梯度
print(x.grad)  # 梯度值

requires_grad=True参数告诉PyTorch需要追踪该Tensor的所有操作,以便后续计算梯度。


Tensor在深度学习中的应用

1. 神经网络中的数据表示

在深度学习中,不同类型的数据用不同维度的Tensor表示:

数据类型 Tensor维度 示例形状 说明
标量 0维 [] 损失值、准确率
向量 1维 [batch_size] 批量标量值
矩阵 2维 [batch_size, feature_size] 批量特征向量
图像 3维 [channels, height, width] 单张图像
图像批次 4维 [batch_size, channels, height, width] 批量图像
视频 5维 [batch_size, channels, frames, height, width] 视频数据

2. 计算图的构建

Tensor和自动求导共同构成了PyTorch的计算图:

复制代码
输入Tensor → 操作1 → 中间Tensor → 操作2 → 输出Tensor
      ↓                           ↓
   梯度计算 ←--- 反向传播 ←--- 损失计算

每个Tensor不仅存储数据,还存储了创建它的操作历史,这使得自动求导成为可能。

3. 内存优化技巧

  1. 使用合适的数据类型:训练时可用float32,推理时尝试float16
  2. 利用共享存储:避免不必要的数据复制
  3. 及时释放内存 :使用del关键字和torch.cuda.empty_cache()
  4. 使用原地操作 :如x.add_(y)而不是x = x + y

下一篇预告

在深入理解了PyTorch的Tensor基础之后,我们将正式进入深度学习模型的构建:

机器学习算法原理与实践-入门(八):基于PyTorch框架的线性回归

我们将使用PyTorch重新实现线性回归模型,体验现代深度学习框架的便利性,并理解自动求导如何简化模型训练过程。

相关推荐
zbdx不知名菜鸡2 小时前
SwanLab 在监控什么?
人工智能·算法·机器学习
2301_822782822 小时前
嵌入式C++调试技术
开发语言·c++·算法
2301_776508722 小时前
实时信号处理库
开发语言·c++·算法
hans汉斯2 小时前
基于污点分析的PHP应用威胁检测平台
开发语言·人工智能·算法·yolo·目标检测·php·无人机
爱思德学术2 小时前
IEEE会议,录用率25.2%!CCF推荐学术会议(C)
计算机网络·算法·编程·软件工程·软件需求
大尚来也2 小时前
Java 线程池深度解析:ThreadPoolExecutor 七大参数与核心原理
java·python·算法
cpp_25012 小时前
P8395 [CCC 2022 S1] Good Fours and Good Fives
数据结构·c++·算法·动态规划·图论·题解·洛谷
Flying pigs~~2 小时前
BERT及其变体、GPT、ELMo
人工智能·深度学习·自然语言处理·大模型·bert·文本分析处理
枫叶林FYL2 小时前
【自然语言处理 NLP】深度学习与表示学习
人工智能·深度学习·机器学习