PyTorch,一个好用的深度学习框架。
1. 深度学习与 PyTorch 简介
1.1 什么是深度学习?
在我们日常生活中,我们的大脑会通过大量的学习和训练来识别事物,比如区分猫和狗。这种能力依赖于 神经网络,它由大量的神经元组成,能够处理复杂的信息。
深度学习(Deep Learning) 是一种受生物神经网络启发的人工智能方法,它能够通过 神经网络(Neural Network) 自动学习数据中的特征,并用于分类、预测等任务。
1.2 PyTorch 的核心优势
PyTorch 是一个广泛使用的深度学习框架,它的特点包括:
- 动态图计算 vs 静态计算图:与 TensorFlow(早期版本)不同,PyTorch 采用动态图计算,代码更直观,调试更方便。
-
动态计算图(Dynamic Computation Graph) 和 静态计算图(Static Computation Graph) 是深度学习框架中两种不同的计算图构建方式,它们的区别可以用 “实时绘图” vs “蓝图施工” 来形象理解。
特性 动态图(PyTorch) 静态图(TensorFlow 早期版本) 构建时机 代码执行时实时构建(边运行边画图) 运行前先定义完整计算图(先画蓝图再施工) 灵活性 支持条件分支、循环等复杂逻辑(自由调整) 需提前定义所有路径(固定结构) 调试难度 可直接打印中间变量(透明可见) 需通过会话(Session)调试(黑箱操作) 性能优化 运行时优化受限(灵活性优先) 可全局优化(适合部署) 典型应用场景 研究、快速实验、可变长度输入(如 NLP) 工业部署、固定计算流程(如推荐系统) -
动态图的直观示例
想象你在搭建乐高模型:
# PyTorch 动态图示例:可以实时修改结构 for data in dataset: x = torch.randn(3) # 输入数据 if x.sum() > 0: # 动态分支 y = x * 2 else: y = x + 1 loss = y.mean() # 实时构建计算图 loss.backward() # 反向传播
-
静态图的局限与突破
早期 TensorFlow 的静态图需要预先定义完整流程:
# TensorFlow 静态图示例(旧版本) import tensorflow as tf # 必须预先定义所有可能的分支 x = tf.placeholder(tf.float32, shape=[None, 3]) condition = tf.reduce_sum(x) > 0 y = tf.cond(condition, lambda: x * 2, lambda: x + 1) # 所有分支需提前声明 loss = tf.reduce_mean(y) with tf.Session() as sess: result = sess.run(loss, feed_dict={x: data}) # 运行时无法修改结构
- 为什么 PyTorch 选择动态图?
-
符合直觉的编程方式
动态图的代码就像普通 Python 代码一样逐行执行,可插入 print 语句调试中间结果:
x = torch.tensor([1.0], requires_grad=True) y = x * 2 print(y) # 输出 tensor([2.], grad_fn=<MulBackward0>) y.backward() # 随时查看梯度
-
处理可变长度数据
在自然语言处理(NLP)中,每个句子的长度不同,动态图可以为每个样本单独构建计算图:
# 处理不同长度的句子 for sentence in text_data: words = sentence.split() embeddings = lookup_table(words) # 动态调整嵌入层维度 output = model(embeddings) # 无需填充(padding)
-
快速原型开发
研究者可以自由修改网络结构,例如在循环中动态添加层:
class DynamicNet(nn.Module): def __init__(self): super().__init__() self.layers = nn.ModuleList() def forward(self, x): for i in range(random.randint(1,5)): # 动态深度! x = nn.Linear(10,10)(x) x = torch.relu(x) return x
-
-
动态图的代价
尽管动态图灵活,但也有一些缺点:
- 性能损失:每次前向传播都重新构建计算图,无法进行全局优化
- 部署难度:动态逻辑难以导出为静态模型(可通过 TorchScript 转换解决)
-
- GPU 加速:PyTorch 允许代码在 CPU 和 GPU 之间无缝切换,加快计算速度。
- 生态丰富:PyTorch 提供了
TorchVision
(图像处理)、TorchText
(自然语言处理)、TorchAudio
(语音处理)等工具,适用于多个领域。- 社区生态:Hugging Face Transformers(最流行的 NLP 库)、PyTorch Lightning(简化训练流程)、TorchServe(生产环境部署工具)
2. 虚拟环境与 PyTorch 安装
2.1 为什么需要虚拟环境?
在机器学习和深度学习项目中,我们通常会安装不同的 Python 依赖库,而这些库可能存在 版本冲突。虚拟环境(Virtual Environment)允许我们在不同的项目中隔离 Python 依赖,保证项目的稳定性。
想象你是一个化学家,你的实验室里有很多不同的试剂。如果你把所有试剂混在一个瓶子里,可能会发生危险的化学反应。同样,在计算机环境中,不同的 Python 依赖可能会相互影响,导致项目无法运行。
2.2 详细安装步骤(适用于 Windows/macOS/Linux)
1. 创建虚拟环境
# 创建虚拟环境
python -m venv pytorch_env
2. 激活虚拟环境
# Windows
pytorch_env\Scripts\activate
# macOS/Linux
source pytorch_env/bin/activate
3. 安装 PyTorch
我们可以使用 PyTorch 官方网站的安装命令生成器(https://pytorch.org/get-started/locally/)。
安装 PyTorch(根据硬件环境选择适合的版本):
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
4. 验证安装是否成功
import torch
print(torch.__version__) # 打印 PyTorch 版本
print(torch.cuda.is_available()) # 检查 GPU 支持
print(torch.rand(3,3).to('cuda')) # 测试 GPU 计算
如果输出 True
,说明 PyTorch 已成功检测到 GPU。
3. PyTorch 核心概念和基础操作
3.1 什么是 Tensor?
在 PyTorch 中,Tensor(张量) 是数据的基本存储单位,类似于 NumPy 数组,但具有更强的计算能力。Tensor 主要用于存储数据,并可以在 CPU 或 GPU 上进行计算。
3.2 Tensor 和 NumPy 的区别:
- NumPy:只支持 CPU 计算,不支持 GPU 加速。
- PyTorch Tensor:可以在 GPU 上加速计算,并支持自动梯度计算。
3.3 创建 Tensor
import torch
# 创建一个 2x3 的矩阵(手动指定数据)
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
print("张量 x:", x)
# 创建全 0 和全 1 的张量
zeros = torch.zeros(3, 3) # 3x3 的全零矩阵
ones = torch.ones(2, 2) # 2x2 的全 1 矩阵
print("全 0 张量:", zeros)
print("全 1 张量:", ones)
# 创建随机数张量
rand_tensor = torch.rand(4, 4) # 4x4 的随机数矩阵
print("随机张量:", rand_tensor)
说明:
torch.tensor(data)
:直接使用 Python 列表或 NumPy 数组创建 Tensor。torch.zeros(shape)
:创建指定形状的全零张量。torch.ones(shape)
:创建指定形状的全 1 张量。torch.rand(shape)
:创建指定形状的随机数张量,范围在[0,1]
之间。
3.4 Tensor 的基本运算
x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6], [7, 8]])
# 张量加法(两种方式)
print("x + y:", x + y)
print("torch.add(x, y):", torch.add(x, y))
# 逐元素乘法(Hadamard 乘积)
print("x * y:", x * y)
# 矩阵乘法(点积)
print("矩阵乘法 torch.matmul(x, y):", torch.matmul(x, y))
# 求均值和求和
print("x 的均值:", x.float().mean())
print("x 的总和:", x.sum())
说明:
x + y
和torch.add(x, y)
:计算逐元素加法。x * y
:计算逐元素乘法。torch.matmul(x, y)
:计算矩阵乘法(点积)。x.mean()
:计算所有元素的均值。x.sum()
:计算所有元素的总和。
3.5 Tensor 的变形、切片和转换
# 变形(Reshape)
tensor = torch.arange(12) # 创建一个包含 0~11 的一维张量
print("原始张量:", tensor)
reshaped_tensor = tensor.view(3, 4) # 变形成 3x4 矩阵
print("变形后的张量:", reshaped_tensor)
# 维度变换(Transpose)
x = torch.rand(2, 3)
y = x.t() # 转置操作
print("原始张量:", x)
print("转置后的张量:", y)
# 切片(Slicing)
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("原始张量:", tensor)
print("第一行:", tensor[0]) # 选择第一行
print("第一列:", tensor[:, 0]) # 选择第一列
print("子矩阵:", tensor[0:2, 1:]) # 选择子矩阵
# 数据类型转换(dtype Conversion)
tensor = torch.tensor([1.2, 3.4, 5.6])
print("原始数据类型:", tensor.dtype)
int_tensor = tensor.int() # 转换为整数类型
print("转换后的数据类型:", int_tensor.dtype)
这些操作对于处理图像数据、神经网络输入、模型优化等非常重要。
3.6 Tensor 的拼接、广播、克隆
# 拼接(Concatenation)
a = torch.rand(2, 3)
b = torch.rand(2, 3)
concat_dim0 = torch.cat((a, b), dim=0) # 在行方向拼接
concat_dim1 = torch.cat((a, b), dim=1) # 在列方向拼接
print("拼接后(按行):", concat_dim0)
print("拼接后(按列):", concat_dim1)
# 广播机制(Broadcasting)
x = torch.rand(3, 1)
y = torch.rand(1, 4)
result = x + y # PyTorch 自动扩展形状以匹配
print("广播结果:", result)
# 克隆(Cloning)
tensor = torch.rand(3, 3)
clone_tensor = tensor.clone() # 深拷贝,不共享内存
print("原始张量:", tensor)
print("克隆张量:", clone_tensor)
3.7 Tensor 的索引、掩码、填充
# 张量索引
x = torch.tensor([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
print("第一行:", x[0]) # 选择第一行
print("第一列:", x[:, 0]) # 选择第一列
print("子矩阵:", x[0:2, 1:]) # 选择子矩阵
# 掩码操作(提取符合条件的元素)
x = torch.tensor([1, 2, 3, 4, 5])
mask = x > 3 # 生成布尔掩码
filtered_x = x[mask] # 仅保留大于 3 的元素
print("大于 3 的元素:", filtered_x)
# 填充(Padding)
import torch.nn.functional as F
x = torch.tensor([[1, 2], [3, 4]])
padded_x = F.pad(x, (1, 1, 1, 1), mode='constant', value=0) # 在四周填充 0
print("填充后的张量:", padded_x)
这些操作在 数据预处理、特征工程、深度学习计算 中非常重要,能够提高计算效率,减少不必要的数据复制。
4. 自动求导(Autograd)
4.1 Autograd 介绍
在深度学习中,模型的训练依赖于反向传播(Backpropagation) 计算参数的梯度,从而更新模型的权重。PyTorch 提供了 autograd
模块,它可以自动计算梯度,使得训练神经网络更加高效。autograd
是 深度学习模型训练的关键,确保梯度计算正确无误。
主要概念:
requires_grad=True
:指定张量是否需要计算梯度。backward()
:执行反向传播计算梯度。.grad
:存储梯度信息。detach()
:阻止张量跟踪梯度。
4.2 计算梯度示例
import torch
# 创建需要计算梯度的张量
x = torch.randn(3, requires_grad=True)
print("x:", x)
# 定义计算公式 y = x^2
y = x ** 2
print("y:", y)
# 计算 z = y 的和
z = y.sum()
print("z:", z)
# 反向传播计算梯度
z.backward()
# 输出梯度值,梯度是 dy/dx = 2x
print("x 的梯度:", x.grad)
说明:
x
是一个 需要计算梯度的张量。y = x^2
计算了每个元素的平方。z = y.sum()
计算y
的总和,这是为了计算z
对x
的梯度。z.backward()
计算dz/dx
,并将梯度存储在x.grad
中。- 根据微积分公式
dy/dx = 2x
,因此x.grad
的值应该是2*x
。
4.3 阻止梯度计算
有时候,我们不希望计算某些张量的梯度(例如,在预测或冻结某些网络层时)。可以使用 detach()
或 with torch.no_grad()
。
x = torch.randn(3, requires_grad=True)
y = x ** 2
# 方法 1:使用 detach()
y_detached = y.detach()
print("y_detached:", y_detached)
# 方法 2:使用 with torch.no_grad()
with torch.no_grad():
y_no_grad = x ** 2
print("y_no_grad:", y_no_grad)
说明:
detach()
生成一个新的张量,但不会计算梯度。torch.no_grad()
作用域内的计算不会影响梯度。
4.4 清除梯度
默认情况下,PyTorch 的 grad
会累积梯度,因此每次计算前需要清除梯度,以防止梯度计算错误。
x = torch.randn(3, requires_grad=True)
y = x ** 2
z = y.sum()
z.backward()
print("第一次计算的梯度:", x.grad)
# 清除梯度
x.grad.zero_()
z = (x ** 3).sum()
z.backward()
print("清除梯度后的新计算:", x.grad)
为什么要清除梯度?
- 如果不清除,梯度会累加,影响训练结果。
x.grad.zero_()
是 原地操作,可以避免不必要的计算开销。
4.5 在深度学习模型中应用 Autograd
在训练神经网络时,我们通常会执行以下步骤:
- 前向传播(Forward Propagation):计算模型的输出。
- 计算损失(Loss Computation):比较预测值和真实标签。
- 反向传播(Backward Propagation):计算梯度。
- 更新参数(Optimization):使用优化器更新模型参数。
import torch
import torch.nn as nn
import torch.optim as optim
# 创建一个简单的线性模型
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.linear = nn.Linear(1, 1) # 输入 1 维,输出 1 维
def forward(self, x):
return self.linear(x)
# 初始化模型、损失函数和优化器
model = SimpleNet()
criterion = nn.MSELoss() # 均方误差损失函数
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 生成数据
torch.manual_seed(0)
x_train = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y_train = torch.tensor([[2.0], [4.0], [6.0], [8.0]])
# 训练模型
for epoch in range(100):
optimizer.zero_grad() # 清空梯度
y_pred = model(x_train) # 前向传播
loss = criterion(y_pred, y_train) # 计算损失
loss.backward() # 反向传播(计算梯度)
optimizer.step() # 更新参数
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}')
解释:
model(x_train)
:执行前向传播计算预测值。criterion(y_pred, y_train)
:计算损失值。loss.backward()
:计算所有参数的梯度。optimizer.step()
:根据梯度更新参数。optimizer.zero_grad()
:清空之前计算的梯度,避免梯度累积。
在 深度学习训练 过程中,autograd
负责自动计算梯度并应用优化,使得训练流程更加高效和灵活。
5. 构建神经网络
5.1 神经网络基础
-
神经网络的基本概念
神经网络由 输入层(Input Layer)、隐藏层(Hidden Layers)和输出层(Output Layer) 组成。
- 输入层:接收原始数据(如图像像素)。
- 隐藏层:由多个神经元组成,进行数据特征提取。
- 输出层:生成最终的分类或回归结果。
-
数学表达
在神经网络中,每个神经元的计算可以表示为:
\[y=f(Wx+b)\]其中:
- $x$ 是输入数据,$W$ 是权重矩阵, $b$ 是偏置项。
- $f(\cdot)$ 是 激活函数,如 ReLU、Sigmoid、Softmax 等。
-
激活函数的作用
激活函数用于引入非线性,使神经网络能够学习复杂的特征。
-
ReLU(修正线性单元):
\[f(x) = \max(0, x)\]计算简单,适用于深度神经网络。
-
Softmax 函数(用于多分类任务):
\[f_i(x) = \frac{e^{x_i}}{\sum_{j} e^{x_j}}\]
-
5.2 载入数据集(MNIST 手写数字集)
from torchvision import datasets, transforms
# 定义数据预处理流程
transform = transforms.Compose([
# 将 PIL 图片转换为 PyTorch 张量(范围从 0-255 变为 0-1)
transforms.ToTensor(),
# 归一化:使数据均值为 0.1307,标准差为 0.3081,以提高训练稳定性
transforms.Normalize((0.1307,), (0.3081,)) # 归一化
])
# 下载并加载 MNIST 数据集(如果数据集未下载,会自动下载)
train_set = datasets.MNIST('./data', train=True, download=True, transform=transform)
# 使用 DataLoader 进行批量加载数据
# batch_size=64:每个批次包含 64 张图片
# shuffle=True:在每个 epoch 之前打乱数据,以提高模型泛化能力
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
说明:
transforms.ToTensor()
:将图像转换为 PyTorch 张量。transforms.Normalize((0.1307,), (0.3081,))
:对数据进行归一化,提高训练稳定性。batch_size=64
:每次训练使用 64 张图片。
5.3 定义神经网络
iimport torch.nn as nn
# 定义一个神经网络模型
class Net(nn.Module):
def __init__(self):
super().__init__()
# 将输入的 28x28 图像展平为 1D 向量 (784 维)
self.flatten = nn.Flatten()
# 第一个全连接层:输入 784 维(28x28),输出 128 维
self.linear1 = nn.Linear(28*28, 128)
# 激活函数 ReLU:增加网络的非线性表达能力
self.relu = nn.ReLU()
# 第二个全连接层:输入 128 维,输出 10 维(对应 10 个类别)
self.linear2 = nn.Linear(128, 10)
def forward(self, x):
# 展平输入数据,使其适应全连接层
x = self.flatten(x)
# 通过第一个全连接层
x = self.linear1(x)
# 应用 ReLU 激活函数
x = self.relu(x)
# 通过第二个全连接层,输出最终分类结果
return self.linear2(x)
# 将模型移到 GPU 上(如果可用)
model = Net().to('cuda')
说明:
nn.Flatten()
:将 28×28 的图像展平为 784 维输入。nn.Linear(28*28, 128)
:第一个全连接层,输入 784 维,输出 128 维。nn.ReLU()
:激活函数,提供非线性能力。nn.Linear(128, 10)
:最终输出 10 维,对应 10 个类别(数字 0-9)。
5.4 训练神经网络
import torch.optim as optim
# 定义损失函数(交叉熵损失)
# 适用于多分类问题,计算预测值与真实标签之间的误差
criterion = nn.CrossEntropyLoss()
# 选择优化器(Adam)
# 作用:更新模型参数以最小化损失
# lr=0.001 代表学习率,影响模型收敛速度
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型 10 轮(Epochs)
for epoch in range(10):
# 遍历训练数据集
for images, labels in train_loader:
# 将数据移动到 GPU(如果可用)
images, labels = images.to('cuda'), labels.to('cuda')
# 清空之前计算的梯度,防止梯度累积影响优化
optimizer.zero_grad()
# 前向传播:计算模型预测值
outputs = model(images)
# 计算损失值(预测值与真实值的误差)
loss = criterion(outputs, labels)
# 反向传播:计算损失对模型参数的梯度
loss.backward()
# 更新模型参数(基于计算出的梯度)
optimizer.step()
# 打印当前轮次的损失值
print(f'Epoch [{epoch+1}/10], Loss: {loss.item():.4f}')
详细步骤:
- 前向传播(Forward Propagation):
outputs = model(images)
计算模型预测值。
- 计算损失(Loss Computation):
criterion(outputs, labels)
计算模型预测值与真实值之间的误差。
- 反向传播(Backward Propagation):
loss.backward()
计算每个参数的梯度。
- 优化(参数更新):
optimizer.step()
更新模型参数。
- 清除梯度:
optimizer.zero_grad()
避免梯度累积。
5.5 评估神经网络
correct = 0
total = 0
# 关闭梯度计算,提高推理速度
with torch.no_grad():
for images, labels in train_loader:
images, labels = images.to('cuda'), labels.to('cuda')
outputs = model(images)
_, predicted = torch.max(outputs, 1) # 选取最大概率对应的类别
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'模型在训练集上的准确率: {accuracy:.2f}%')
说明:
torch.no_grad()
:关闭梯度计算,节省内存并加快推理。torch.max(outputs, 1)
:找到模型预测的类别。- 计算模型的准确率。
总结
通过本次讲座,我们从 PyTorch 的基础知识与安装入手,学习了张量的运算,了解了神经网络以及如何搭建神经网络、进行数据预处理,并进行了实战 — 训练了一个用于 MNIST 手写数字识别的模型。这是深度学习的第一步,也是通往更高级任务的起点。
未来,我们可以探索更多高级内容,例如:
- 卷积神经网络(CNN):用于图像分类任务,如人脸识别。
- 循环神经网络(RNN)与 Transformer:用于自然语言处理,如机器翻译。
- 强化学习(RL):用于机器人控制或游戏 AI。
- 模型优化:使用更复杂的优化器、数据增强和正则化技巧提升模型表现。
希望这些内容能够帮助大家理解 PyTorch 的核心概念,并激发大家在深度学习领域的探索兴趣!如有任何问题,欢迎交流和讨论。祝大家在 AI 之路上不断进步!