拥抱张量:深度学习框架 PyTorch 入门指南
近年来,深度学习以前所未有的速度发展,成为人工智能领域最耀眼的技术之一。而在众多深度学习框架中,PyTorch 凭借其“Pythonic”的风格、灵活的动态计算图以及强大的社区支持,迅速崛起,成为研究者和工程师们的宠儿。如果你正准备踏入深度学习的大门,那么学习 PyTorch 无疑是一个极好的选择。
本文将带你从零开始,系统地认识 PyTorch 的核心概念,并通过简单的代码示例,让你快速掌握 PyTorch 的基本用法,为你迈向更复杂的深度学习应用打下坚实的基础。
为什么选择 PyTorch?
在深入学习之前,我们先了解一下 PyTorch 的一些突出优势:
- Pythonic 风格: PyTorch 的 API 设计与 Python 语言高度契合,语法直观易懂,使得有 Python 基础的用户能够快速上手。
- 动态计算图: 与 TensorFlow 早期版本使用的静态图不同,PyTorch 使用动态计算图。这意味着你可以在运行时构建、修改和执行计算图,这在处理变长输入、序列模型或需要频繁控制流程的场景时提供了极大的灵活性,也使得调试变得更加容易。
- 强大的自动微分(Autograd): PyTorch 的 Autograd 引擎能够自动计算张量上的所有运算的梯度,这极大地简化了神经网络训练中的反向传播过程。
- 丰富的工具和库: PyTorch 拥有一个庞大的生态系统,包括用于计算机视觉的
torchvision
、用于自然语言处理的torchtext
等,以及大量第三方库和预训练模型。 - 强大的社区支持: PyTorch 社区活跃,文档齐全,遇到问题很容易找到解决方案。
- 易于调试: 动态图的特性让你可以像调试普通 Python 代码一样调试 PyTorch 模型,这比静态图框架要方便得多。
准备工作:安装 PyTorch
在开始编程之前,你需要先安装 PyTorch。最推荐的方式是通过 pip 或 conda 包管理器。请访问 PyTorch 官方网站 (pytorch.org),根据你的操作系统、Python 版本以及是否需要 GPU 支持来选择合适的安装命令。
例如,使用 conda 安装支持 CUDA 11.7 的版本(假设你已经安装了 Anaconda 或 Miniconda 并有兼容的 NVIDIA GPU):
bash
conda install pytorch torchvision torchaudio cudatoolkit=11.7 -c pytorch -c conda-forge
如果只需要 CPU 版本:
bash
conda install pytorch torchvision torchaudio cpuonly -c pytorch
安装完成后,你可以在 Python 环境中通过 import torch
来验证。
PyTorch 核心概念
PyTorch 的学习主要围绕几个核心概念展开:张量 (Tensor)、自动微分 (Autograd)、神经网络模块 (torch.nn)、优化器 (torch.optim) 以及数据加载 (torch.utils.data)。我们将逐一介绍。
1. 张量 (Tensors)
张量是 PyTorch 的基本数据结构,类似于 NumPy 的 ndarray
,但张量可以在 GPU 上进行加速计算。你可以将其理解为多维数组。
创建张量:
你可以通过多种方式创建张量:
“`python
import torch
import numpy as np
直接从数据创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f”从列表创建张量:\n{x_data}\n”)
从 NumPy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(f”从 NumPy 数组创建张量:\n{x_np}\n”)
创建特定形状的张量
x_ones = torch.ones(2, 2) # 全1张量
print(f”全1张量:\n{x_ones}\n”)
x_zeros = torch.zeros(2, 2) # 全0张量
print(f”全0张量:\n{x_zeros}\n”)
x_rand = torch.rand(2, 2) # 随机张量
print(f”随机张量:\n{x_rand}\n”)
创建与另一个张量具有相同形状和数据类型的张量
x_like = torch.ones_like(x_data) # 创建一个形状和 dtype 与 x_data 相同的全1张量
print(f”与 x_data 类似的张量:\n{x_like}\n”)
“`
张量的属性:
张量有一些重要的属性可以帮助你了解它的信息:
python
tensor = torch.ones(4, 4)
print(f"形状 (Shape): {tensor.shape}")
print(f"数据类型 (Data type): {tensor.dtype}")
print(f"设备 (Device): {tensor.device}") # CPU 或 CUDA
张量的操作:
张量支持丰富的操作,包括数学运算、索引切片、重塑等,许多操作与 NumPy 非常相似。
“`python
tensor = torch.ones(4, 4)
索引切片 (与 NumPy 类似)
print(f”第一行: {tensor[0]}”)
print(f”第一列: {tensor[:, 0]}”)
print(f”最后一行最后一列: {tensor[-1, -1]}”)
连接张量
t1 = torch.cat([tensor, tensor], dim=0) # 按行连接
print(f”按行连接:\n{t1}\n”)
t2 = torch.cat([tensor, tensor], dim=1) # 按列连接
print(f”按列连接:\n{t2}\n”)
矩阵乘法 (非常常用)
注意:这里的 tensor 是 4×4,不能与自身进行矩阵乘法(除非转置)
假设有两个张量 a (2×3) 和 b (3×4)
a = torch.randn(2, 3)
b = torch.randn(3, 4)
matrix_mult_result = torch.matmul(a, b) # 或 a @ b (Python 3.5+)
print(f”矩阵乘法结果形状: {matrix_mult_result.shape}\n”) # 应为 2×4
元素级乘法
element_mult_result = tensor.mul(tensor) # 或 tensor * tensor
print(f”元素级乘法:\n{element_mult_result}\n”)
单元素张量
agg = tensor.sum() # 求和
print(f”求和结果: {agg}”)
agg_item = agg.item() # 获取单元素张量的 Python 数值
print(f”求和结果 (Python): {agg_item}\n”)
重塑 (改变张量的形状)
reshaped_tensor = tensor.view(16) # 展平为一维张量
print(f”重塑后的形状: {reshaped_tensor.shape}\n”)
“`
张量与 NumPy 的转换:
PyTorch 张量和 NumPy 数组可以方便地互相转换。
“`python
从 PyTorch 张量到 NumPy 数组
numpy_array = tensor.numpy()
print(f”PyTorch 张量转换为 NumPy 数组:\n{numpy_array}\n”)
注意:如果张量在 GPU 上,需要先移回 CPU
gpu_tensor = tensor.to(“cuda”)
numpy_array_from_gpu = gpu_tensor.cpu().numpy()
从 NumPy 数组到 PyTorch 张量 (前面已展示过)
“`
GPU 加速:
如果你的机器有可用的 NVIDIA GPU 并正确安装了 CUDA 版本的 PyTorch,你可以将张量移动到 GPU 上进行计算,从而获得显著的速度提升。
“`python
if torch.cuda.is_available():
device = “cuda”
else:
device = “cpu”
print(f”当前设备: {device}”)
将张量移动到设备
tensor_gpu = tensor.to(device)
print(f”移动到 {device} 的张量:\n{tensor_gpu}\n”)
在 GPU 上执行运算
tensor_gpu_result = tensor_gpu * 2
print(f”在 {device} 上运算的结果:\n{tensor_gpu_result}\n”)
“`
2. 自动微分 (Autograd)
Autograd 是 PyTorch 的核心特性之一,它能够自动记录对张量的所有操作,并构建计算图,以便在需要时自动计算梯度。这是实现反向传播和训练神经网络的关键。
要让 PyTorch 跟踪一个张量的计算历史并计算其梯度,你需要在创建张量时设置 requires_grad=True
。对于神经网络中的参数(权重和偏置),PyTorch 会默认设置这个属性。
“`python
import torch
创建一个需要计算梯度的张量
x = torch.ones(5, requires_grad=True)
print(f”需要梯度的张量 x: {x}\n”)
进行一些操作,构建计算图
y = x + 2
z = y * y * 3
out = z.mean()
print(f”中间结果 y: {y}\n”)
print(f”中间结果 z: {z}\n”)
print(f”最终结果 out: {out}\n”)
执行反向传播 (Backward)
out 是一个单元素张量,可以直接调用 backward()
如果 out 是一个非单元素张量,需要传入一个与 out 形状相同的张量作为梯度参数
out.backward()
查看梯度 (d(out)/dx)
print(f”x 的梯度: {x.grad}\n”)
解释:out = mean(z) = mean(3 * y^2) = mean(3 * (x+2)^2)
对于 x 的某个元素 x_i,out = (1/5) * sum(3 * (x_j+2)^2)
d(out)/d(x_i) = (1/5) * d(3 * (x_i+2)^2) / d(x_i)
= (1/5) * 3 * 2 * (x_i+2) * 1
= (6/5) * (x_i+2)
由于 x 是全 1 的张量,x_i = 1,所以梯度应该是 (6/5) * (1+2) = (6/5) * 3 = 18/5 = 3.6
结果与输出一致
停止梯度跟踪:
在评估模型(推理)时,通常不需要计算梯度,这时可以使用 torch.no_grad() 上下文管理器,或者使用 .detach() 方法。
print(“进入 no_grad 上下文:”)
with torch.no_grad():
y_nograd = x + 2
print(f”no_grad 下的 y_nograd.requires_grad: {y_nograd.requires_grad}\n”) # 应为 False
使用 .detach()
y_detached = x.detach()
print(f”detach 后的 y_detached.requires_grad: {y_detached.requires_grad}\n”) # 应为 False
.detach() 创建一个新的张量,它与原张量共享数据,但不再参与梯度计算。
“`
3. 神经网络模块 (torch.nn)
torch.nn
模块提供了构建神经网络所需的各种层(Layer)和常用函数。在 PyTorch 中,所有的神经网络层都继承自 nn.Module
基类。
一个典型的 nn.Module
子类需要实现两个方法:
* __init__
:用于初始化层、定义子模块、注册参数等。
* forward
:定义前向传播的计算过程。
“`python
import torch.nn as nn
import torch.nn.functional as F # 函数式接口,如激活函数,通常没有可学习参数
定义一个简单的神经网络
class SimpleNN(nn.Module):
def init(self):
super(SimpleNN, self).init() # 调用父类构造函数
# 定义全连接层 (线性层): 输入10个特征,输出5个特征
self.fc1 = nn.Linear(10, 5)
# 定义激活函数
self.relu = nn.ReLU()
# 定义另一个全连接层: 输入5个特征,输出1个特征 (例如,用于回归或二分类)
self.fc2 = nn.Linear(5, 1)
def forward(self, x):
# 前向传播过程
x = self.fc1(x) # 通过第一个全连接层
x = self.relu(x) # 通过 ReLU 激活函数
x = self.fc2(x) # 通过第二个全连接层
return x
创建模型实例
model = SimpleNN()
print(f”定义的简单神经网络结构:\n{model}\n”)
模型会自动注册其所有 nn.Module 子模块的参数
print(“模型的可学习参数:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.shape}”)
示例输入数据 (batch_size=1, 输入特征数=10)
dummy_input = torch.randn(1, 10)
进行前向传播
output = model(dummy_input)
print(f”模型输出形状: {output.shape}\n”) # 应为 torch.Size([1, 1])
“`
nn.Module
包含了各种预定义的层,如:
* nn.Linear
(全连接层)
* nn.Conv2d
(二维卷积层)
* nn.MaxPool2d
(二维最大池化层)
* nn.ReLU
, nn.Sigmoid
, nn.Tanh
(激活函数)
* nn.BatchNorm2d
(二维批标准化层)
* nn.LSTM
, nn.GRU
(循环神经网络层)
* 等等。
4. 优化器 (torch.optim)
优化器的作用是根据模型参数的梯度来更新参数,以最小化损失函数。torch.optim
模块提供了多种常用的优化算法,如随机梯度下降(SGD)、Adam、Adagrad 等。
在使用优化器时,你需要指定要优化的参数(通常是模型的 parameters()
)和学习率等超参数。
“`python
import torch.optim as optim
假设我们有前面定义的 model
获取模型的参数
model_parameters = model.parameters()
定义一个随机梯度下降 (SGD) 优化器
第一个参数是模型的参数,第二个是学习率 (lr)
optimizer = optim.SGD(model_parameters, lr=0.01)
print(f”定义的优化器: {optimizer}\n”)
定义一个 Adam 优化器 (另一种常用的优化器)
optimizer = optim.Adam(model.parameters(), lr=0.001)
在训练循环中,通常会执行以下步骤:
1. 计算损失 (loss)
2. 清零之前计算的梯度: optimizer.zero_grad()
3. 计算当前梯度的反向传播: loss.backward()
4. 根据梯度更新参数: optimizer.step()
“`
5. 损失函数 (Loss Functions)
损失函数衡量模型的输出与真实标签之间的差异。在训练过程中,我们的目标就是最小化这个损失。torch.nn
模块也包含了一些常用的损失函数。
“`python
定义一个均方误差损失函数 (用于回归任务)
loss_fn_mse = nn.MSELoss()
定义一个交叉熵损失函数 (常用于分类任务)
注意:交叉熵损失函数内部通常包含了 Softmax
loss_fn_ce = nn.CrossEntropyLoss()
假设 model_output 是模型的输出,target 是真实标签
model_output = model(dummy_input) # 假设 dummy_input shape (batch_size, input_dim)
target = torch.randn(1, 1) # 假设回归目标形状 (batch_size, output_dim)
计算损失
loss = loss_fn_mse(model_output, target)
print(f”计算的损失值: {loss.item()}\n”)
在训练循环中,这个 loss 会用于调用 loss.backward() 来计算梯度。
“`
6. 数据加载 (torch.utils.data)
在训练神经网络时,我们通常需要处理大量数据,并以小批量(Batch)的形式送入模型。torch.utils.data
模块提供了 Dataset
和 DataLoader
两个重要类来简化数据处理过程。
Dataset
:表示数据集的抽象类。你需要自定义一个继承自Dataset
的类,并实现__len__
(返回数据集大小) 和__getitem__
(根据索引返回单个样本及其标签) 方法。DataLoader
:包装Dataset
,提供批量加载、数据混洗(Shuffling)、并行加载等功能。
“`python
from torch.utils.data import Dataset, DataLoader
示例:自定义一个简单的数字数据集
class CustomDataset(Dataset):
def init(self, data, labels):
self.data = data
self.labels = labels
def __len__(self):
return len(self.data) # 数据集大小
def __getitem__(self, idx):
# 根据索引返回一个样本和其标签
return self.data[idx], self.labels[idx]
创建一些模拟数据
data = torch.randn(100, 10) # 100个样本,每个样本10个特征
labels = torch.randint(0, 2, (100,)) # 100个标签 (例如,0或1的二分类)
创建数据集实例
dataset = CustomDataset(data, labels)
print(f”数据集大小: {len(dataset)}\n”)
sample, label = dataset[0] # 获取第一个样本
print(f”第一个样本形状: {sample.shape}, 标签: {label}\n”)
创建数据加载器
dataset: 要加载的数据集
batch_size: 每批次样本数
shuffle: 是否在每个 epoch 开始时混洗数据
num_workers: 用于数据加载的子进程数 (0表示在主进程加载)
dataloader = DataLoader(dataset, batch_size=10, shuffle=True, num_workers=0)
遍历数据加载器
print(“遍历数据加载器:”)
for batch_idx, (batch_data, batch_labels) in enumerate(dataloader):
print(f”Batch {batch_idx}: 数据形状 {batch_data.shape}, 标签形状 {batch_labels.shape}”)
if batch_idx == 2: # 只打印前3个批次
break
print(“\n数据加载器遍历结束.\n”)
“`
DataLoader
极大地简化了训练过程中的数据批量处理,是进行大规模训练的必备工具。
将核心概念整合:一个简单的训练示例
现在,让我们将上面介绍的核心概念组合起来,编写一个非常简单的训练循环,用于拟合一条直线(线性回归的简化版)。
目标:找到参数 w
和 b
,使得 y = w*x + b
尽可能接近真实数据。
“`python
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt # 用于可视化 (可选)
1. 准备数据
模拟真实数据: y = 2*x + 1 + 噪声
torch.manual_seed(42) # 设置随机种子以便结果可复现
x_true = torch.randn(100, 1) * 10 # 100个 x 值
y_true = 2 * x_true + 1 + torch.randn(100, 1) * 2 # 对应的 y 值加上噪声
将数据移动到 GPU (如果可用)
if torch.cuda.is_available():
device = “cuda”
else:
device = “cpu”
x_true = x_true.to(device)
y_true = y_true.to(device)
print(f”数据使用的设备: {device}”)
2. 定义模型
一个简单的线性模型: y = wx + b
class LinearRegressionModel(nn.Module):
def init(self):
super(LinearRegressionModel, self).init()
# nn.Linear(input_features, output_features)
self.linear = nn.Linear(1, 1)
def forward(self, x):
return self.linear(x)
model = LinearRegressionModel().to(device) # 创建模型并移动到设备
print(f”模型结构:\n{model}\n”)
3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失
optimizer = optim.SGD(model.parameters(), lr=0.01) # SGD 优化器,学习率 0.01
4. 训练循环
epochs = 1000 # 训练轮次
print(“开始训练…”)
for epoch in range(epochs):
# 前向传播
outputs = model(x_true)
loss = criterion(outputs, y_true)
# 反向传播和优化
optimizer.zero_grad() # 清零之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 打印训练信息
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
print(“训练结束.\n”)
5. 查看学习到的参数和模型预测
print(“学习到的模型参数:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.data.cpu().numpy()}”) # 将参数移回 CPU 查看
预测
在进行预测或评估时,通常不需要计算梯度
with torch.no_grad():
predicted = model(x_true).cpu().numpy() # 预测并移回 CPU
可视化 (可选)
plt.plot(x_true.cpu().numpy(), y_true.cpu().numpy(), ‘o’, label=’Original data’)
plt.plot(x_true.cpu().numpy(), predicted, label=’Fitted line’)
plt.legend()
plt.show()
验证学习到的参数
期望 w 接近 2,b 接近 1
learned_w = model.linear.weight.data.cpu().numpy()[0][0]
learned_b = model.linear.bias.data.cpu().numpy()[0]
print(f”\n学习到的 w: {learned_w:.4f}, b: {learned_b:.4f}”)
“`
在这个示例中,我们展示了 PyTorch 进行基本训练的完整流程:
1. 准备数据(张量形式,并移到指定设备)。
2. 定义模型(继承 nn.Module
)。
3. 定义损失函数。
4. 定义优化器。
5. 进入训练循环,每个 epoch 执行前向传播、计算损失、清零梯度、反向传播、更新参数。
6. 在循环结束后,可以使用训练好的模型进行预测。
进阶方向和后续学习
这仅仅是 PyTorch 的冰山一角。在掌握了上述基础概念后,你可以进一步学习:
- 更复杂的网络结构: 卷积神经网络 (CNN) 用于图像处理,循环神经网络 (RNN) 和 Transformer 用于序列数据 (如文本)。
- 数据处理: 使用
torchvision
,torchtext
等库处理特定领域的数据。 - 模型保存与加载: 如何保存和加载训练好的模型参数或整个模型。
- 迁移学习: 使用预训练模型并在新任务上进行微调。
- GPU 多卡训练: 如何利用多块 GPU 加速训练。
- 可视化工具: 使用 TensorBoard (通过
torch.utils.tensorboard
) 监控训练过程。 - 部署: 将训练好的模型部署到不同的平台 (如移动设备、服务器)。
PyTorch 官方提供了大量优秀的教程 (https://pytorch.org/tutorials/),覆盖了从基础到高级的各种主题,强烈推荐作为后续学习资源。此外,实践是最好的老师,尝试用 PyTorch 解决一些实际问题(如 MNIST 手写数字识别、文本分类等)将帮助你更好地巩固所学知识。
总结
本文带你了解了深度学习框架 PyTorch 的入门基础,包括核心概念:张量、自动微分、nn.Module、优化器和数据加载。通过一个简单的线性回归示例,我们看到了如何将这些组件组合起来构建和训练一个模型。
PyTorch 以其灵活性、易用性和强大的功能,已经成为深度学习领域不可或缺的工具。掌握 PyTorch,你将能够更高效地进行深度学习研究和应用开发。这篇入门指南为你打开了一扇门,勇敢地迈出下一步,在深度学习的世界里探索更多精彩吧!