深度学习框架 PyTorch 入门 – wiki基地


拥抱张量:深度学习框架 PyTorch 入门指南

近年来,深度学习以前所未有的速度发展,成为人工智能领域最耀眼的技术之一。而在众多深度学习框架中,PyTorch 凭借其“Pythonic”的风格、灵活的动态计算图以及强大的社区支持,迅速崛起,成为研究者和工程师们的宠儿。如果你正准备踏入深度学习的大门,那么学习 PyTorch 无疑是一个极好的选择。

本文将带你从零开始,系统地认识 PyTorch 的核心概念,并通过简单的代码示例,让你快速掌握 PyTorch 的基本用法,为你迈向更复杂的深度学习应用打下坚实的基础。

为什么选择 PyTorch?

在深入学习之前,我们先了解一下 PyTorch 的一些突出优势:

  1. Pythonic 风格: PyTorch 的 API 设计与 Python 语言高度契合,语法直观易懂,使得有 Python 基础的用户能够快速上手。
  2. 动态计算图: 与 TensorFlow 早期版本使用的静态图不同,PyTorch 使用动态计算图。这意味着你可以在运行时构建、修改和执行计算图,这在处理变长输入、序列模型或需要频繁控制流程的场景时提供了极大的灵活性,也使得调试变得更加容易。
  3. 强大的自动微分(Autograd): PyTorch 的 Autograd 引擎能够自动计算张量上的所有运算的梯度,这极大地简化了神经网络训练中的反向传播过程。
  4. 丰富的工具和库: PyTorch 拥有一个庞大的生态系统,包括用于计算机视觉的 torchvision、用于自然语言处理的 torchtext 等,以及大量第三方库和预训练模型。
  5. 强大的社区支持: PyTorch 社区活跃,文档齐全,遇到问题很容易找到解决方案。
  6. 易于调试: 动态图的特性让你可以像调试普通 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 模块提供了 DatasetDataLoader 两个重要类来简化数据处理过程。

  • 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 极大地简化了训练过程中的数据批量处理,是进行大规模训练的必备工具。

将核心概念整合:一个简单的训练示例

现在,让我们将上面介绍的核心概念组合起来,编写一个非常简单的训练循环,用于拟合一条直线(线性回归的简化版)。

目标:找到参数 wb,使得 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,你将能够更高效地进行深度学习研究和应用开发。这篇入门指南为你打开了一扇门,勇敢地迈出下一步,在深度学习的世界里探索更多精彩吧!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部